* @license MIT */ declare(strict_types=1); namespace BitBadger\PDODocument\Query; use BitBadger\PDODocument\{Configuration, DocumentException, DocumentIndex, Mode}; use Exception; /** * Queries to define tables and indexes */ class Definition { /** * SQL statement to create a document table * * @param string $name The name of the table (including schema, if applicable) * @return string The CREATE TABLE statement for the document table * @throws Exception If the database mode has not been set */ public static function ensureTable(string $name): string { $dataType = match (Configuration::mode('make create table statement')) { Mode::PgSQL => 'JSONB', Mode::SQLite => 'TEXT', }; return "CREATE TABLE IF NOT EXISTS $name (data $dataType NOT NULL)"; } /** * Split a schema and table name * * @param string $tableName The name of the table, possibly including the schema * @return array|string[] An array with the schema at index 0 and the table name at index 1 */ private static function splitSchemaAndTable(string $tableName): array { $parts = explode('.', $tableName); return sizeof($parts) === 1 ? ["", $tableName] : [$parts[0], $parts[1]]; } /** * SQL statement to create an index on one or more fields in a JSON document * * @param string $tableName The name of the table which should be indexed * @param string $indexName The name of the index to create * @param string[] $fields An array of fields to be indexed; may contain direction (ex. 'salary DESC') * @return string The CREATE INDEX statement to ensure the index exists */ public static function ensureIndexOn(string $tableName, string $indexName, array $fields): string { [, $tbl] = self::splitSchemaAndTable($tableName); $jsonFields = implode(', ', array_map(function (string $field) { $parts = explode(' ', $field); $fieldName = sizeof($parts) === 1 ? $field : $parts[0]; $direction = sizeof($parts) < 2 ? "" : " $parts[1]"; return "(data->>'$fieldName')$direction"; }, $fields)); return "CREATE INDEX IF NOT EXISTS idx_{$tbl}_$indexName ON $tableName ($jsonFields)"; } /** * SQL statement to create a key index for a document table * * @param string $tableName The name of the table whose key should be ensured * @return string The CREATE INDEX statement to ensure the key index exists */ public static function ensureKey(string $tableName): string { return str_replace('INDEX', 'UNIQUE INDEX', self::ensureIndexOn($tableName, 'key', [Configuration::$idField])); } /** * Create a document-wide index on a table (PostgreSQL only) * * @param string $tableName The name of the table on which the document index should be created * @param DocumentIndex $indexType The type of index to be created * @return string The SQL statement to create an index on JSON documents in the specified table * @throws Exception|DocumentException If the database mode is not PostgreSQL */ public static function ensureDocumentIndexOn(string $tableName, DocumentIndex $indexType): string { if (Configuration::mode() <> Mode::PgSQL) { throw new DocumentException('Document indexes are only supported on PostgreSQL'); } [, $tbl] = self::splitSchemaAndTable($tableName); $extraOps = match ($indexType) { DocumentIndex::Full => '', DocumentIndex::Optimized => ' jsonb_path_ops', }; return "CREATE INDEX IF NOT EXISTS idx_{$tbl}_document ON $tableName USING GIN (data$extraOps)"; } }