UNPKG

@directus/schema

Version:

Utility for extracting information about existing DB schema

1,317 lines (1,310 loc) 67.7 kB
//#region src/utils/strip-quotes.ts /** * Strip leading/trailing quotes from a string and handle null values. */ function stripQuotes(value) { if (value === null || value === void 0) return null; const trimmed = value.trim(); if (trimmed.startsWith(`'`) && trimmed.endsWith(`'`) || trimmed.startsWith("\"") && trimmed.endsWith("\"")) return trimmed.slice(1, -1); return value; } //#endregion //#region src/dialects/mysql.ts function rawColumnToColumn$2(rawColumn) { let dataType = rawColumn.COLUMN_TYPE.replace(/\(.*?\)/, ""); if (rawColumn.COLUMN_TYPE.startsWith("tinyint(1)")) dataType = "boolean"; return { name: rawColumn.COLUMN_NAME, table: rawColumn.TABLE_NAME, data_type: dataType, default_value: parseDefaultValue$5(rawColumn.COLUMN_DEFAULT), generation_expression: rawColumn.GENERATION_EXPRESSION || null, max_length: rawColumn.CHARACTER_MAXIMUM_LENGTH, numeric_precision: rawColumn.NUMERIC_PRECISION, numeric_scale: rawColumn.NUMERIC_SCALE, is_generated: !!rawColumn.EXTRA?.endsWith("GENERATED"), is_nullable: rawColumn.IS_NULLABLE === "YES", is_unique: rawColumn.COLUMN_KEY === "UNI", is_indexed: !!rawColumn.INDEX_NAME && rawColumn.INDEX_NAME.length > 0, is_primary_key: rawColumn.CONSTRAINT_NAME === "PRIMARY" || rawColumn.COLUMN_KEY === "PRI", has_auto_increment: rawColumn.EXTRA === "auto_increment", foreign_key_column: rawColumn.REFERENCED_COLUMN_NAME, foreign_key_table: rawColumn.REFERENCED_TABLE_NAME, comment: rawColumn.COLUMN_COMMENT }; } function parseDefaultValue$5(value) { if (value === null || value.trim().toLowerCase() === "null") return null; return stripQuotes(value); } var MySQL = class { knex; constructor(knex) { this.knex = knex; } async overview() { const columns = await this.knex.raw(` SELECT C.TABLE_NAME as table_name, C.COLUMN_NAME as column_name, C.COLUMN_DEFAULT as default_value, C.IS_NULLABLE as is_nullable, C.COLUMN_TYPE as data_type, C.COLUMN_KEY as column_key, C.CHARACTER_MAXIMUM_LENGTH as max_length, C.EXTRA as extra FROM INFORMATION_SCHEMA.COLUMNS AS C LEFT JOIN INFORMATION_SCHEMA.TABLES AS T ON C.TABLE_NAME = T.TABLE_NAME AND C.TABLE_SCHEMA = T.TABLE_SCHEMA WHERE T.TABLE_TYPE = 'BASE TABLE' AND C.TABLE_SCHEMA = ?; `, [this.knex.client.database()]); const overview = {}; for (const column of columns[0]) { if (column.table_name in overview === false) { const primaryKeys = columns[0].filter((nested) => { return nested.table_name === column.table_name && nested.column_key === "PRI"; }); overview[column.table_name] = { primary: primaryKeys.length !== 1 ? void 0 : primaryKeys[0].column_name, columns: {} }; } let dataType = column.data_type.replace(/\(.*?\)/, ""); if (column.data_type.startsWith("tinyint(1)")) dataType = "boolean"; overview[column.table_name].columns[column.column_name] = { ...column, default_value: column.extra === "auto_increment" ? "AUTO_INCREMENT" : parseDefaultValue$5(column.default_value), is_nullable: column.is_nullable === "YES", is_generated: column.extra?.endsWith("GENERATED") ?? false, data_type: dataType }; } return overview; } /** * List all existing tables in the current schema/database */ async tables() { return (await this.knex.select("TABLE_NAME").from("INFORMATION_SCHEMA.TABLES").where({ TABLE_TYPE: "BASE TABLE", TABLE_SCHEMA: this.knex.client.database() })).map(({ TABLE_NAME }) => TABLE_NAME); } async tableInfo(table) { const query = this.knex.select("TABLE_NAME", "ENGINE", "TABLE_SCHEMA", "TABLE_COLLATION", "TABLE_COMMENT").from("information_schema.tables").where({ table_schema: this.knex.client.database(), table_type: "BASE TABLE" }); if (table) { const rawTable = await query.andWhere({ table_name: table }).first(); return { name: rawTable.TABLE_NAME, schema: rawTable.TABLE_SCHEMA, comment: rawTable.TABLE_COMMENT, collation: rawTable.TABLE_COLLATION, engine: rawTable.ENGINE }; } return (await query).map((rawTable) => { return { name: rawTable.TABLE_NAME, schema: rawTable.TABLE_SCHEMA, comment: rawTable.TABLE_COMMENT, collation: rawTable.TABLE_COLLATION, engine: rawTable.ENGINE }; }); } /** * Check if a table exists in the current schema/database */ async hasTable(table) { const result = await this.knex.count({ count: "*" }).from("information_schema.tables").where({ table_schema: this.knex.client.database(), table_name: table }).first(); return result && result.count === 1 || false; } /** * Get all the available columns in the current schema/database. Can be filtered to a specific table */ async columns(table) { const query = this.knex.select("TABLE_NAME", "COLUMN_NAME").from("INFORMATION_SCHEMA.COLUMNS").where({ TABLE_SCHEMA: this.knex.client.database() }); if (table) query.andWhere({ TABLE_NAME: table }); return (await query).map(({ TABLE_NAME, COLUMN_NAME }) => ({ table: TABLE_NAME, column: COLUMN_NAME })); } async columnInfo(table, column) { const query = this.knex.select("c.TABLE_NAME", "c.COLUMN_NAME", "c.COLUMN_DEFAULT", "c.COLUMN_TYPE", "c.CHARACTER_MAXIMUM_LENGTH", "c.IS_NULLABLE", "c.COLUMN_KEY", "c.EXTRA", "c.COLLATION_NAME", "c.COLUMN_COMMENT", "c.NUMERIC_PRECISION", "c.NUMERIC_SCALE", "c.GENERATION_EXPRESSION", "fk.REFERENCED_TABLE_NAME", "fk.REFERENCED_COLUMN_NAME", "fk.CONSTRAINT_NAME", "rc.UPDATE_RULE", "rc.DELETE_RULE", "rc.MATCH_OPTION", "stats.INDEX_NAME").from("INFORMATION_SCHEMA.COLUMNS as c").leftJoin("INFORMATION_SCHEMA.KEY_COLUMN_USAGE as fk", function() { this.on("c.TABLE_NAME", "=", "fk.TABLE_NAME").andOn("fk.COLUMN_NAME", "=", "c.COLUMN_NAME").andOn("fk.CONSTRAINT_SCHEMA", "=", "c.TABLE_SCHEMA"); }).leftJoin("INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS as rc", function() { this.on("rc.TABLE_NAME", "=", "fk.TABLE_NAME").andOn("rc.CONSTRAINT_NAME", "=", "fk.CONSTRAINT_NAME").andOn("rc.CONSTRAINT_SCHEMA", "=", "fk.CONSTRAINT_SCHEMA"); }).leftJoin("INFORMATION_SCHEMA.STATISTICS as stats", function() { this.on("stats.TABLE_SCHEMA", "=", "c.TABLE_SCHEMA").andOn("stats.TABLE_NAME", "=", "c.TABLE_NAME").andOn("stats.COLUMN_NAME", "=", "c.COLUMN_NAME").andOnVal("stats.NON_UNIQUE", 1).andOnVal("stats.SEQ_IN_INDEX", 1); }).where({ "c.TABLE_SCHEMA": this.knex.client.database() }); if (table) query.andWhere({ "c.TABLE_NAME": table }); if (column) return rawColumnToColumn$2(await query.andWhere({ "c.column_name": column }).first()); return (await query).map(rawColumnToColumn$2).sort((column$1) => +!column$1.foreign_key_column).filter((column$1, index, records) => { return records.findIndex((_column) => { return column$1.name === _column.name && column$1.table === _column.table; }) === index; }); } /** * Check if a table exists in the current schema/database */ async hasColumn(table, column) { const result = await this.knex.count("*", { as: "count" }).from("information_schema.columns").where({ table_schema: this.knex.client.database(), table_name: table, column_name: column }).first(); return !!(result && result.count); } /** * Get the primary key column for the given table */ async primary(table) { const results = await this.knex.raw(`SHOW KEYS FROM ?? WHERE Key_name = 'PRIMARY'`, table); if (results && results.length && results[0].length) return results[0][0]["Column_name"]; return null; } async foreignKeys(table) { const result = await this.knex.raw(` SELECT DISTINCT rc.TABLE_NAME AS 'table', kcu.COLUMN_NAME AS 'column', rc.REFERENCED_TABLE_NAME AS 'foreign_key_table', kcu.REFERENCED_COLUMN_NAME AS 'foreign_key_column', rc.CONSTRAINT_NAME AS 'constraint_name', rc.UPDATE_RULE AS on_update, rc.DELETE_RULE AS on_delete FROM information_schema.referential_constraints AS rc JOIN information_schema.key_column_usage AS kcu ON rc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME AND kcu.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA WHERE rc.CONSTRAINT_SCHEMA = ?; `, [this.knex.client.database()]); if (table) return result?.[0]?.filter((row) => row.table === table).map((row) => ({ ...row })) ?? []; return result?.[0]?.map((row) => ({ ...row })) ?? []; } }; //#endregion //#region src/dialects/postgres.ts /** * Converts Postgres default value to JS * Eg `'example'::character varying` => `example` */ function parseDefaultValue$4(value) { if (value === null) return null; if (value.startsWith("nextval(")) return value; value = value.split("::")[0] ?? null; if (value?.trim().toLowerCase() === "null") return null; return stripQuotes(value); } var Postgres = class { knex; schema; explodedSchema; constructor(knex) { this.knex = knex; const config = knex.client.config; if (!config.searchPath) { this.schema = "public"; this.explodedSchema = [this.schema]; } else if (typeof config.searchPath === "string") { this.schema = config.searchPath; this.explodedSchema = [config.searchPath]; } else { this.schema = config.searchPath[0]; this.explodedSchema = config.searchPath; } } /** * Set the schema to be used in other methods */ withSchema(schema) { this.schema = schema; this.explodedSchema = [this.schema]; return this; } async overview() { const bindings = this.explodedSchema.map(() => "?").join(","); const [columnsResult, primaryKeysResult] = await Promise.all([this.knex.raw(` SELECT c.table_name , c.column_name , c.column_default as default_value , c.data_type , c.character_maximum_length as max_length , c.is_generated = 'ALWAYS' is_generated , CASE WHEN c.is_identity = 'YES' THEN true ELSE false END is_identity , CASE WHEN c.is_nullable = 'YES' THEN true ELSE false END is_nullable FROM information_schema.columns c LEFT JOIN information_schema.tables t ON c.table_name = t.table_name WHERE t.table_type = 'BASE TABLE' AND c.table_schema IN (${bindings}); `, this.explodedSchema), this.knex.raw(` SELECT relname as table_name , pg_attribute.attname as column_name FROM pg_index , pg_class , pg_attribute , pg_namespace WHERE indrelid = pg_class.oid AND nspname IN (${bindings}) AND pg_class.relnamespace = pg_namespace.oid AND pg_attribute.attrelid = pg_class.oid AND pg_attribute.attnum = ANY (pg_index.indkey) AND indisprimary AND indnatts = 1 AND relkind != 'S' `, this.explodedSchema)]); const columns = columnsResult.rows; const primaryKeys = primaryKeysResult.rows; let geometryColumns = []; if ((await this.knex.raw(`SELECT oid FROM pg_proc WHERE proname = 'postgis_version'`)).rows.length > 0) geometryColumns = (await this.knex.raw(`WITH geometries as ( select * from geometry_columns union select * from geography_columns ) SELECT f_table_name as table_name , f_geometry_column as column_name , type as data_type FROM geometries g JOIN information_schema.tables t ON g.f_table_name = t.table_name AND t.table_type = 'BASE TABLE' WHERE f_table_schema in (${bindings}) `, this.explodedSchema)).rows; const overview = {}; for (const column of columns) { if (column.is_identity || column.default_value?.startsWith("nextval(")) column.default_value = "AUTO_INCREMENT"; else column.default_value = parseDefaultValue$4(column.default_value); if (column.table_name in overview === false) overview[column.table_name] = { columns: {}, primary: void 0 }; if (["point", "polygon"].includes(column.data_type)) column.data_type = "unknown"; overview[column.table_name].columns[column.column_name] = column; } for (const { table_name, column_name } of primaryKeys) if (overview[table_name]) overview[table_name].primary = column_name; else console.error(`Could not set primary key "${column_name}" for unknown table "${table_name}"`); for (const { table_name, column_name, data_type } of geometryColumns) if (overview[table_name]) if (overview[table_name].columns[column_name]) overview[table_name].columns[column_name].data_type = data_type; else console.error(`Could not set data type "${data_type}" for unknown column "${column_name}" in table "${table_name}"`); else console.error(`Could not set geometry column "${column_name}" for unknown table "${table_name}"`); return overview; } /** * List all existing tables in the current schema/database */ async tables() { const schemaIn = this.explodedSchema.map((schemaName) => `${this.knex.raw("?", [schemaName])}::regnamespace`); return (await this.knex.raw(` SELECT rel.relname AS name FROM pg_class rel WHERE rel.relnamespace IN (${schemaIn}) AND rel.relkind = 'r' ORDER BY rel.relname `)).rows.map((row) => row.name); } async tableInfo(table) { const schemaIn = this.explodedSchema.map((schemaName) => `${this.knex.raw("?", [schemaName])}::regnamespace`); const bindings = []; if (table) bindings.push(table); const result = await this.knex.raw(` SELECT rel.relnamespace::regnamespace::text AS schema, rel.relname AS name, des.description AS comment FROM pg_class rel LEFT JOIN pg_description des ON rel.oid = des.objoid AND des.objsubid = 0 WHERE rel.relnamespace IN (${schemaIn}) ${table ? "AND rel.relname = ?" : ""} AND rel.relkind = 'r' ORDER BY rel.relname `, bindings); if (table) return result.rows[0]; return result.rows; } /** * Check if a table exists in the current schema/database */ async hasTable(table) { const schemaIn = this.explodedSchema.map((schemaName) => `${this.knex.raw("?", [schemaName])}::regnamespace`); return (await this.knex.raw(` SELECT rel.relname AS name FROM pg_class rel WHERE rel.relnamespace IN (${schemaIn}) AND rel.relkind = 'r' AND rel.relname = ? ORDER BY rel.relname `, [table])).rows.length > 0; } /** * Get all the available columns in the current schema/database. Can be filtered to a specific table */ async columns(table) { const bindings = []; if (table) bindings.push(table); const schemaIn = this.explodedSchema.map((schemaName) => `${this.knex.raw("?", [schemaName])}::regnamespace`); return (await this.knex.raw(` SELECT att.attname AS column, rel.relname AS table FROM pg_attribute att LEFT JOIN pg_class rel ON att.attrelid = rel.oid WHERE rel.relnamespace IN (${schemaIn}) ${table ? "AND rel.relname = ?" : ""} AND rel.relkind = 'r' AND att.attnum > 0 AND NOT att.attisdropped; `, bindings)).rows; } async columnInfo(table, column) { const { knex } = this; const bindings = []; if (table) bindings.push(table); if (column) bindings.push(column); const schemaIn = this.explodedSchema.map((schemaName) => `${this.knex.raw("?", [schemaName])}::regnamespace`); const majorVersion = (await this.knex.raw(`SHOW server_version`)).rows?.[0]?.server_version?.split(".")?.[0] ?? 10; let generationSelect = ` NULL AS generation_expression, pg_get_expr(ad.adbin, ad.adrelid) AS default_value, FALSE AS is_generated, `; if (Number(majorVersion) >= 12) generationSelect = ` CASE WHEN att.attgenerated = 's' THEN pg_get_expr(ad.adbin, ad.adrelid) ELSE null END AS generation_expression, CASE WHEN att.attgenerated = '' THEN pg_get_expr(ad.adbin, ad.adrelid) ELSE null END AS default_value, att.attgenerated = 's' AS is_generated, `; const [columns, constraints] = await Promise.all([knex.raw(` SELECT att.attname AS name, rel.relname AS table, rel.relnamespace::regnamespace::text as schema, att.atttypid::regtype::text AS data_type, ix_rel.relname as index_name, NOT att.attnotnull AS is_nullable, ${generationSelect} CASE WHEN att.atttypid IN (1042, 1043) THEN (att.atttypmod - 4)::int4 WHEN att.atttypid IN (1560, 1562) THEN (att.atttypmod)::int4 ELSE NULL END AS max_length, des.description AS comment, CASE att.atttypid WHEN 21 THEN 16 WHEN 23 THEN 32 WHEN 20 THEN 64 WHEN 1700 THEN CASE WHEN atttypmod = -1 THEN NULL ELSE (((atttypmod - 4) >> 16) & 65535)::int4 END WHEN 700 THEN 24 WHEN 701 THEN 53 ELSE NULL END AS numeric_precision, CASE WHEN atttypid IN (21, 23, 20) THEN 0 WHEN atttypid = 1700 THEN CASE WHEN atttypmod = -1 THEN NULL ELSE ((atttypmod - 4) & 65535)::int4 END ELSE null END AS numeric_scale FROM pg_attribute att LEFT JOIN pg_class rel ON att.attrelid = rel.oid LEFT JOIN pg_attrdef ad ON (att.attrelid, att.attnum) = (ad.adrelid, ad.adnum) LEFT JOIN pg_description des ON (att.attrelid, att.attnum) = (des.objoid, des.objsubid) LEFT JOIN LATERAL ( SELECT indexrelid FROM pg_index ix WHERE att.attrelid = ix.indrelid AND att.attnum = ALL(ix.indkey) AND ix.indisunique = false LIMIT 1 ) ix ON true LEFT JOIN pg_class ix_rel ON ix_rel.oid=ix.indexrelid WHERE rel.relnamespace IN (${schemaIn}) ${table ? "AND rel.relname = ?" : ""} ${column ? "AND att.attname = ?" : ""} AND rel.relkind = 'r' AND att.attnum > 0 AND NOT att.attisdropped ORDER BY rel.relname, att.attnum; `, bindings), knex.raw(` SELECT con.contype AS type, rel.relname AS table, att.attname AS column, frel.relnamespace::regnamespace::text AS foreign_key_schema, frel.relname AS foreign_key_table, fatt.attname AS foreign_key_column, CASE WHEN con.contype = 'p' THEN pg_get_serial_sequence(att.attrelid::regclass::text, att.attname) != '' ELSE NULL END AS has_auto_increment FROM pg_constraint con LEFT JOIN pg_class rel ON con.conrelid = rel.oid LEFT JOIN pg_class frel ON con.confrelid = frel.oid LEFT JOIN pg_attribute att ON att.attrelid = con.conrelid AND att.attnum = con.conkey[1] LEFT JOIN pg_attribute fatt ON fatt.attrelid = con.confrelid AND fatt.attnum = con.confkey[1] WHERE con.connamespace IN (${schemaIn}) AND array_length(con.conkey, 1) <= 1 AND (con.confkey IS NULL OR array_length(con.confkey, 1) = 1) ${table ? "AND rel.relname = ?" : ""} ${column ? "AND att.attname = ?" : ""} `, bindings)]); const parsedColumns = columns.rows.map((col) => { const constraintsForColumn = constraints.rows.filter((constraint) => constraint.table === col.table && constraint.column === col.name); const foreignKeyConstraint = constraintsForColumn.find((constraint) => constraint.type === "f"); return { name: col.name, table: col.table, data_type: col.data_type, default_value: parseDefaultValue$4(col.default_value), generation_expression: col.generation_expression, max_length: col.max_length, numeric_precision: col.numeric_precision, numeric_scale: col.numeric_scale, is_generated: col.is_generated, is_nullable: col.is_nullable, is_unique: constraintsForColumn.some((constraint) => ["u", "p"].includes(constraint.type)), is_indexed: !!col.index_name && col.index_name.length > 0, is_primary_key: constraintsForColumn.some((constraint) => constraint.type === "p"), has_auto_increment: constraintsForColumn.some((constraint) => constraint.has_auto_increment), foreign_key_schema: foreignKeyConstraint?.foreign_key_schema ?? null, foreign_key_table: foreignKeyConstraint?.foreign_key_table ?? null, foreign_key_column: foreignKeyConstraint?.foreign_key_column ?? null, comment: col.comment }; }); if (table && column) return parsedColumns[0]; if (!((await this.knex.raw(`SELECT oid FROM pg_proc WHERE proname = 'postgis_version'`)).rows.length > 0)) return parsedColumns; for (const column$1 of parsedColumns) if (["point", "polygon"].includes(column$1.data_type)) column$1.data_type = "unknown"; const query = this.knex.with("geometries", this.knex.raw(` select * from geometry_columns union select * from geography_columns `)).select({ table: "f_table_name", name: "f_geometry_column", data_type: "type" }).from("geometries").whereIn("f_table_schema", this.explodedSchema); if (table) query.andWhere("f_table_name", table); if (column) { const parsedColumn = parsedColumns[0]; const geometry = await query.andWhere("f_geometry_column", column).first(); if (geometry) parsedColumn.data_type = geometry.data_type; } const geometries = await query; for (const column$1 of parsedColumns) { const geometry = geometries.find((geometry$1) => { return column$1.name == geometry$1.name && column$1.table == geometry$1.table; }); if (geometry) column$1.data_type = geometry.data_type; } if (table && column) return parsedColumns[0]; return parsedColumns; } /** * Check if the given table contains the given column */ async hasColumn(table, column) { const schemaIn = this.explodedSchema.map((schemaName) => `${this.knex.raw("?", [schemaName])}::regnamespace`); return (await this.knex.raw(` SELECT att.attname AS column, rel.relname AS table FROM pg_attribute att LEFT JOIN pg_class rel ON att.attrelid = rel.oid WHERE rel.relnamespace IN (${schemaIn}) AND rel.relname = ? AND att.attname = ? AND rel.relkind = 'r' AND att.attnum > 0 AND NOT att.attisdropped; `, [table, column])).rows; } /** * Get the primary key column for the given table */ async primary(table) { const schemaIn = this.explodedSchema.map((schemaName) => `${this.knex.raw("?", [schemaName])}::regnamespace`); return (await this.knex.raw(` SELECT att.attname AS column FROM pg_constraint con LEFT JOIN pg_class rel ON con.conrelid = rel.oid LEFT JOIN pg_attribute att ON att.attrelid = con.conrelid AND att.attnum = con.conkey[1] WHERE con.connamespace IN (${schemaIn}) AND con.contype = 'p' AND array_length(con.conkey, 1) <= 1 AND rel.relname = ? `, [table])).rows?.[0]?.column ?? null; } async foreignKeys(table) { const schemaIn = this.explodedSchema.map((schemaName) => `${this.knex.raw("?", [schemaName])}::regnamespace`); const bindings = []; if (table) bindings.push(table); return (await this.knex.raw(` SELECT con.conname AS constraint_name, rel.relname AS table, att.attname AS column, frel.relnamespace::regnamespace::text AS foreign_key_schema, frel.relname AS foreign_key_table, fatt.attname AS foreign_key_column, CASE con.confupdtype WHEN 'r' THEN 'RESTRICT' WHEN 'c' THEN 'CASCADE' WHEN 'n' THEN 'SET NULL' WHEN 'd' THEN 'SET DEFAULT' WHEN 'a' THEN 'NO ACTION' ELSE NULL END AS on_update, CASE con.confdeltype WHEN 'r' THEN 'RESTRICT' WHEN 'c' THEN 'CASCADE' WHEN 'n' THEN 'SET NULL' WHEN 'd' THEN 'SET DEFAULT' WHEN 'a' THEN 'NO ACTION' ELSE NULL END AS on_delete FROM pg_constraint con LEFT JOIN pg_class rel ON con.conrelid = rel.oid LEFT JOIN pg_class frel ON con.confrelid = frel.oid LEFT JOIN pg_attribute att ON att.attrelid = con.conrelid AND att.attnum = con.conkey[1] LEFT JOIN pg_attribute fatt ON fatt.attrelid = con.confrelid AND fatt.attnum = con.confkey[1] WHERE con.connamespace IN (${schemaIn}) AND array_length(con.conkey, 1) <= 1 AND (con.confkey IS NULL OR array_length(con.confkey, 1) = 1) AND con.contype = 'f' ${table ? "AND rel.relname = ?" : ""} `, bindings)).rows; } }; //#endregion //#region src/dialects/cockroachdb.ts /** * Converts CockroachDB default value to JS * Eg `'example'::character varying` => `example` */ function parseDefaultValue$3(value) { if (value === null) return null; if (value.startsWith("nextval(")) return value; value = value.split("::")[0] ?? null; if (value?.trim().toLowerCase() === "null") return null; return stripQuotes(value); } var CockroachDB = class { knex; schema; explodedSchema; constructor(knex) { this.knex = knex; const config = knex.client.config; if (!config.searchPath) { this.schema = "public"; this.explodedSchema = [this.schema]; } else if (typeof config.searchPath === "string") { this.schema = config.searchPath; this.explodedSchema = [config.searchPath]; } else { this.schema = config.searchPath[0]; this.explodedSchema = config.searchPath; } } /** * Set the schema to be used in other methods */ withSchema(schema) { this.schema = schema; this.explodedSchema = [this.schema]; return this; } async overview() { const [columnsResult, primaryKeysResult] = await Promise.all([this.knex.raw(` SELECT c.table_name , c.column_name , c.column_default as default_value , c.data_type , c.character_maximum_length as max_length , c.is_generated = 'ALWAYS' is_generated , CASE WHEN c.is_identity = 'YES' THEN true ELSE false END is_identity , CASE WHEN c.is_nullable = 'YES' THEN true ELSE false END is_nullable FROM information_schema.columns c LEFT JOIN information_schema.tables t ON c.table_name = t.table_name WHERE t.table_type = 'BASE TABLE' AND c.table_schema IN (?); `, [this.explodedSchema.join(",")]), this.knex.raw(` SELECT relname as table_name , pg_attribute.attname as column_name FROM pg_index , pg_class , pg_attribute , pg_namespace WHERE indrelid = pg_class.oid AND nspname IN (?) AND pg_class.relnamespace = pg_namespace.oid AND pg_attribute.attrelid = pg_class.oid AND pg_attribute.attnum = ANY (pg_index.indkey) AND indisprimary AND indnatts = 1 AND relkind != 'S' `, [this.explodedSchema.join(",")])]); const columns = columnsResult.rows; const primaryKeys = primaryKeysResult.rows; let geometryColumns = []; if ((await this.knex.raw(`SELECT oid FROM pg_proc WHERE proname = 'postgis_version'`)).rows.length > 0) geometryColumns = (await this.knex.raw(`WITH geometries as ( select * from geometry_columns union select * from geography_columns ) SELECT f_table_name as table_name , f_geometry_column as column_name , type as data_type FROM geometries g JOIN information_schema.tables t ON g.f_table_name = t.table_name AND t.table_type = 'BASE TABLE' WHERE f_table_schema in (?) `, [this.explodedSchema.join(",")])).rows; const overview = {}; for (const column of columns) { if (column.is_identity || column.default_value?.startsWith("nextval(")) column.default_value = "AUTO_INCREMENT"; else column.default_value = parseDefaultValue$3(column.default_value); if (column.table_name in overview === false) overview[column.table_name] = { columns: {}, primary: void 0 }; if (["point", "polygon"].includes(column.data_type)) column.data_type = "unknown"; overview[column.table_name].columns[column.column_name] = column; } for (const { table_name, column_name } of primaryKeys) if (overview[table_name]) overview[table_name].primary = column_name; else console.error(`Could not set primary key "${column_name}" for unknown table "${table_name}"`); for (const { table_name, column_name, data_type } of geometryColumns) if (overview[table_name]) if (overview[table_name].columns[column_name]) overview[table_name].columns[column_name].data_type = data_type; else console.error(`Could not set data type "${data_type}" for unknown column "${column_name}" in table "${table_name}"`); else console.error(`Could not set geometry column "${column_name}" for unknown table "${table_name}"`); return overview; } /** * List all existing tables in the current schema/database */ async tables() { return (await this.knex.select("tablename").from("pg_catalog.pg_tables").whereIn("schemaname", this.explodedSchema)).map(({ tablename }) => tablename); } async tableInfo(table) { const query = this.knex.select("table_name", "table_schema", this.knex.select(this.knex.raw("obj_description(oid)")).from("pg_class").where({ relkind: "r" }).andWhere({ relname: "table_name" }).as("table_comment")).from("information_schema.tables").whereIn("table_schema", this.explodedSchema).andWhereRaw(`"table_catalog" = current_database()`).andWhere({ table_type: "BASE TABLE" }).orderBy("table_name", "asc"); if (table) { const rawTable = await query.andWhere({ table_name: table }).limit(1).first(); return { name: rawTable.table_name, schema: rawTable.table_schema, comment: rawTable.table_comment }; } return (await query).map((rawTable) => { return { name: rawTable.table_name, schema: rawTable.table_schema, comment: rawTable.table_comment }; }); } /** * Check if a table exists in the current schema/database */ async hasTable(table) { const subquery = this.knex.select().from("information_schema.tables").whereIn("table_schema", this.explodedSchema).andWhere({ table_name: table }); return (await this.knex.select(this.knex.raw("exists (?)", [subquery])).first())?.exists || false; } /** * Get all the available columns in the current schema/database. Can be filtered to a specific table */ async columns(table) { const query = this.knex.select("table_name", "column_name").from("information_schema.columns").whereIn("table_schema", this.explodedSchema); if (table) query.andWhere({ table_name: table }); return (await query).map(({ table_name, column_name }) => ({ table: table_name, column: column_name })); } async columnInfo(table, column) { const { knex } = this; const bindings = []; if (table) bindings.push(table); if (column) bindings.push(column); const schemaIn = this.explodedSchema.map((schemaName) => `${this.knex.raw("?", [schemaName])}::regnamespace`); const [columns, constraints] = await Promise.all([knex.raw(` SELECT *, CASE WHEN res.is_generated THEN ( SELECT generation_expression FROM information_schema.columns WHERE table_schema = res.schema AND table_name = res.table AND column_name = res.name ) ELSE NULL END AS generation_expression FROM ( SELECT att.attname AS name, rel.relname AS table, rel.relnamespace::regnamespace::text AS schema, format_type(att.atttypid, null) AS data_type, ix_rel.relname as index_name, NOT att.attnotnull AS is_nullable, CASE WHEN att.attgenerated = '' THEN pg_get_expr(ad.adbin, ad.adrelid) ELSE null END AS default_value, att.attgenerated = 's' AS is_generated, CASE WHEN att.atttypid IN (1042, 1043) THEN (att.atttypmod - 4)::int4 WHEN att.atttypid IN (1560, 1562) THEN (att.atttypmod)::int4 ELSE NULL END AS max_length, des.description AS comment, CASE att.atttypid WHEN 21 THEN 16 WHEN 23 THEN 32 WHEN 20 THEN 64 WHEN 1700 THEN CASE WHEN atttypmod = -1 THEN NULL ELSE (((atttypmod - 4) >> 16) & 65535)::int4 END WHEN 700 THEN 24 WHEN 701 THEN 53 ELSE NULL END AS numeric_precision, CASE WHEN atttypid IN (21, 23, 20) THEN 0 WHEN atttypid = 1700 THEN CASE WHEN atttypmod = -1 THEN NULL ELSE ((atttypmod - 4) & 65535)::int4 END ELSE null END AS numeric_scale FROM pg_attribute att LEFT JOIN pg_class rel ON att.attrelid = rel.oid LEFT JOIN pg_attrdef ad ON (att.attrelid, att.attnum) = (ad.adrelid, ad.adnum) LEFT JOIN pg_description des ON (att.attrelid, att.attnum) = (des.objoid, des.objsubid) LEFT JOIN LATERAL ( SELECT indexrelid FROM pg_index ix WHERE att.attrelid = ix.indrelid AND att.attnum = ALL(ix.indkey) AND ix.indisunique = false LIMIT 1 ) ix ON true LEFT JOIN pg_class ix_rel ON ix_rel.oid=ix.indexrelid WHERE rel.relnamespace IN (${schemaIn}) ${table ? "AND rel.relname = ?" : ""} ${column ? "AND att.attname = ?" : ""} AND rel.relkind = 'r' AND att.attnum > 0 AND NOT att.attisdropped ORDER BY rel.relname, att.attnum) res; `, bindings), knex.raw(` SELECT con.contype AS type, rel.relname AS table, att.attname AS column, frel.relnamespace::regnamespace::text AS foreign_key_schema, frel.relname AS foreign_key_table, fatt.attname AS foreign_key_column FROM pg_constraint con LEFT JOIN pg_class rel ON con.conrelid = rel.oid LEFT JOIN pg_class frel ON con.confrelid = frel.oid LEFT JOIN pg_attribute att ON att.attrelid = con.conrelid AND att.attnum = con.conkey[1] LEFT JOIN pg_attribute fatt ON fatt.attrelid = con.confrelid AND fatt.attnum = con.confkey[1] WHERE con.connamespace IN (${schemaIn}) AND array_length(con.conkey, 1) <= 1 AND (con.confkey IS NULL OR array_length(con.confkey, 1) = 1) ${table ? "AND rel.relname = ?" : ""} ${column ? "AND att.attname = ?" : ""} `, bindings)]); const parsedColumns = columns.rows.map((col) => { const constraintsForColumn = constraints.rows.filter((constraint) => constraint.table === col.table && constraint.column === col.name); const foreignKeyConstraint = constraintsForColumn.find((constraint) => constraint.type === "f"); return { name: col.name, table: col.table, data_type: col.data_type, default_value: parseDefaultValue$3(col.default_value), generation_expression: col.generation_expression, max_length: col.max_length, numeric_precision: col.numeric_precision, numeric_scale: col.numeric_scale, is_generated: col.is_generated, is_nullable: col.is_nullable, is_unique: constraintsForColumn.some((constraint) => ["u", "p"].includes(constraint.type)), is_indexed: !!col.index_name?.length && col.index_name.length > 0, is_primary_key: constraintsForColumn.some((constraint) => constraint.type === "p"), has_auto_increment: ["integer", "bigint"].includes(col.data_type) && (col.default_value?.startsWith("nextval(") ?? false), foreign_key_schema: foreignKeyConstraint?.foreign_key_schema ?? null, foreign_key_table: foreignKeyConstraint?.foreign_key_table ?? null, foreign_key_column: foreignKeyConstraint?.foreign_key_column ?? null, comment: col.comment }; }); for (const column$1 of parsedColumns) if (["point", "polygon"].includes(column$1.data_type)) column$1.data_type = "unknown"; if (!((await this.knex.raw(`SELECT oid FROM pg_proc WHERE proname = 'postgis_version'`)).rows.length > 0)) { if (table && column) return parsedColumns[0]; return parsedColumns; } const query = this.knex.with("geometries", this.knex.raw(` select * from geometry_columns union select * from geography_columns `)).select({ table: "f_table_name", name: "f_geometry_column", data_type: "type" }).from("geometries").whereIn("f_table_schema", this.explodedSchema); if (table) query.andWhere("f_table_name", table); if (column) { const parsedColumn = parsedColumns[0]; const geometry = await query.andWhere("f_geometry_column", column).first(); if (geometry) parsedColumn.data_type = geometry.data_type; return parsedColumn; } const geometries = await query; for (const column$1 of parsedColumns) { const geometry = geometries.find((geometry$1) => { return column$1.name == geometry$1.name && column$1.table == geometry$1.table; }); if (geometry) column$1.data_type = geometry.data_type; } return parsedColumns; } /** * Check if the given table contains the given column */ async hasColumn(table, column) { const subquery = this.knex.select().from("information_schema.columns").whereIn("table_schema", this.explodedSchema).andWhere({ table_name: table, column_name: column }); return (await this.knex.select(this.knex.raw("exists (?)", [subquery])).first())?.exists || false; } /** * Get the primary key column for the given table */ async primary(table) { const result = await this.knex.select("information_schema.key_column_usage.column_name").from("information_schema.key_column_usage").leftJoin("information_schema.table_constraints", "information_schema.table_constraints.constraint_name", "information_schema.key_column_usage.constraint_name").whereIn("information_schema.table_constraints.table_schema", this.explodedSchema).andWhere({ "information_schema.table_constraints.constraint_type": "PRIMARY KEY", "information_schema.table_constraints.table_name": table }).first(); return result ? result.column_name : null; } async foreignKeys(table) { const rowsWithoutQuotes = (await this.knex.raw(` SELECT c.conrelid::regclass::text AS "table", ( SELECT STRING_AGG(a.attname, ',' ORDER BY t.seq) FROM ( SELECT ROW_NUMBER() OVER (ROWS UNBOUNDED PRECEDING) AS seq, attnum FROM UNNEST(c.conkey) AS t (attnum)) AS t INNER JOIN pg_attribute AS a ON a.attrelid = c.conrelid AND a.attnum = t.attnum) AS "column", tt.name AS foreign_key_table, ( SELECT STRING_AGG(QUOTE_IDENT(a.attname), ',' ORDER BY t.seq) FROM ( SELECT ROW_NUMBER() OVER (ROWS UNBOUNDED PRECEDING) AS seq, attnum FROM UNNEST(c.confkey) AS t (attnum)) AS t INNER JOIN pg_attribute AS a ON a.attrelid = c.confrelid AND a.attnum = t.attnum) AS foreign_key_column, tt.schema AS foreign_key_schema, c.conname AS constraint_name, CASE confupdtype WHEN 'r' THEN 'RESTRICT' WHEN 'c' THEN 'CASCADE' WHEN 'n' THEN 'SET NULL' WHEN 'd' THEN 'SET DEFAULT' WHEN 'a' THEN 'NO ACTION' ELSE NULL END AS on_update, CASE confdeltype WHEN 'r' THEN 'RESTRICT' WHEN 'c' THEN 'CASCADE' WHEN 'n' THEN 'SET NULL' WHEN 'd' THEN 'SET DEFAULT' WHEN 'a' THEN 'NO ACTION' ELSE NULL END AS on_delete FROM pg_catalog.pg_constraint AS c INNER JOIN ( SELECT pg_class.oid, QUOTE_IDENT(pg_namespace.nspname) AS SCHEMA, QUOTE_IDENT(pg_class.relname) AS name FROM pg_class INNER JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid) AS tf ON tf.oid = c.conrelid INNER JOIN ( SELECT pg_class.oid, QUOTE_IDENT(pg_namespace.nspname) AS SCHEMA, QUOTE_IDENT(pg_class.relname) AS name FROM pg_class INNER JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid) AS tt ON tt.oid = c.confrelid WHERE c.contype = 'f'; `)).rows.map(stripRowQuotes); if (table) return rowsWithoutQuotes.filter((row) => row.table === table); return rowsWithoutQuotes; function stripRowQuotes(row) { return Object.fromEntries(Object.entries(row).map(([key, value]) => { return [key, stripQuotes(value)]; })); } } }; //#endregion //#region src/utils/extract-max-length.ts /** * Extracts the length value out of a given datatype * For example: `varchar(32)` => 32 */ function extractMaxLength(type) { const matches = /\(([^)]+)\)/.exec(type); if (matches && matches.length > 0 && matches[1]) return Number(matches[1]); return null; } //#endregion //#region src/utils/extract-type.ts /** * Extracts the type out of a given datatype * For example: `varchar(32)` => varchar */ function extractType(type) { return type.replace(/[^a-zA-Z]/g, "").toLowerCase(); } //#endregion //#region src/dialects/sqlite.ts function parseDefaultValue$2(value) { if (value === null || value.trim().toLowerCase() === "null") return null; return stripQuotes(value); } var SQLite = class { knex; constructor(knex) { this.knex = knex; } async overview() { const tablesWithAutoIncrementPrimaryKeys = (await this.knex.select("name").from("sqlite_master").whereRaw(`sql LIKE "%AUTOINCREMENT%"`)).map(({ name }) => name); const tables = await this.tables(); const overview = {}; for (const table of tables) { const columns = await this.knex.raw(`PRAGMA table_xinfo(??)`, table); if (table in overview === false) { const primaryKeys = columns.filter((column) => column.pk !== 0); overview[table] = { primary: primaryKeys.length !== 1 ? void 0 : primaryKeys[0].name, columns: {} }; } for (const column of columns) overview[table].columns[column.name] = { table_name: table, column_name: column.name, default_value: column.pk === 1 && tablesWithAutoIncrementPrimaryKeys.includes(table) ? "AUTO_INCREMENT" : parseDefaultValue$2(column.dflt_value), is_nullable: column.notnull == 0, is_generated: column.hidden !== 0, data_type: extractType(column.type), max_length: extractMaxLength(column.type), numeric_precision: null, numeric_scale: null }; } return overview; } /** * List all existing tables in the current schema/database */ async tables() { return (await this.knex.select("name").from("sqlite_master").whereRaw(`type = 'table' AND name NOT LIKE 'sqlite_%'`)).map(({ name }) => name); } async tableInfo(table) { const query = this.knex.select("name", "sql").from("sqlite_master").where({ type: "table" }).andWhereRaw(`name NOT LIKE 'sqlite_%'`); if (table) query.andWhere({ name: table }); let records = await query; records = records.map((table$1) => ({ name: table$1.name, sql: table$1.sql })); if (table) return records[0]; return records; } /** * Check if a table exists in the current schema/database */ async hasTable(table) { return (await this.knex.select(1).from("sqlite_master").where({ type: "table", name: table })).length > 0; } /** * Get all the available columns in the current schema/database. Can be filtered to a specific table */ async columns(table) { if (table) return (await this.knex.raw(`PRAGMA table_xinfo(??)`, table)).map((column) => ({ table, column: column.name })); const tables = await this.tables(); return (await Promise.all(tables.map(async (table$1) => await this.columns(table$1)))).flat(); } async columnInfo(table, column) { const getColumnsForTable = async (table$1) => { const tablesWithAutoIncrementPrimaryKeys = (await this.knex.select("name").from("sqlite_master").whereRaw(`sql LIKE "%AUTOINCREMENT%"`)).map(({ name }) => name); const columns = await this.knex.raw(`PRAGMA table_xinfo(??)`, table$1); const foreignKeys = await this.knex.raw(`PRAGMA foreign_key_list(??)`, table$1); const indexList = await this.knex.raw(`PRAGMA index_list(??)`, table$1); const indexInfoList = await Promise.all(indexList.map((index) => this.knex.raw(`PRAGMA index_info(??)`, index.name))); return columns.map((raw) => { const foreignKey = foreignKeys.find((fk) => fk.from === raw.name); let isUniqueColumn = false; let isIndexedColumn = false; for (let i = 0; i < indexInfoList.length; i++) { if (!indexInfoList[i]?.find((fk) => fk.name === raw.name)) continue; if (indexInfoList[i]?.length !== 1 || !indexList[i]) continue; if (indexList[i].unique === 1) isUniqueColumn = true; else if (indexList[i].name.length > 0) isIndexedColumn = true; if (isUniqueColumn && isIndexedColumn) break; } return { name: raw.name, table: table$1, data_type: extractType(raw.type), default_value: parseDefaultValue$2(raw.dflt_value), max_length: extractMaxLength(raw.type), numeric_precision: null, numeric_scale: null, is_generated: raw.hidden !== 0, generation_expression: null, is_nullable: raw.notnull === 0, is_unique: isUniqueColumn, is_indexed: isIndexedColumn, is_primary_key: raw.pk === 1, has_auto_increment: raw.pk === 1 && tablesWithAutoIncrementPrimaryKeys.includes(table$1), foreign_key_column: foreignKey?.to || null, foreign_key_table: foreignKey?.table || null }; }); }; if (!table) { const tables = await this.tables(); return (await Promise.all(tables.map(async (table$1) => await getColumnsForTable(table$1)))).flat(); } if (table && !column) return await getColumnsForTable(table); return (await getColumnsForTable(table)).find((columnInfo) => columnInfo.name === column); } /** * Check if a table exists in the current schema/database */ async hasColumn(table, column) { let isColumn = false; if ((await this.knex.raw(`SELECT COUNT(*) AS ct FROM pragma_table_xinfo('${table}') WHERE name='${column}'`))[0]["ct"] !== 0) isColumn = true; return isColumn; } /** * Get the primary key column for the given table */ async primary(table) { return (await this.knex.raw(`PRAGMA table_xinfo(??)`, table)).find((col) => col.pk !== 0)?.name || null; } async foreignKeys(table) { if (table) return (await this.knex.raw(`PRAGMA foreign_key_list(??)`, table)).map((key) => ({ table, column: key.from, foreign_key_table: key.table, foreign_key_column: key.to, on_update: key.on_update, on_delete: key.on_delete, constraint_name: null })); const tables = await this.tables(); return (await Promise.all(tables.map(async (table$1) => await this.foreignKeys(table$1)))).flat(); } }; //#endregion //#region src/dialects/oracledb.ts /** * NOTE: Use previous optimizer for better data dictionary performance. */ const OPTIMIZER_FEATURES = "11.2.0.4"; function rawColumnToColumn$1(rawColumn) { const is_generated = rawColumn.VIRTUAL_COLUMN === "YES"; const default_value = parseDefaultValue$1(rawColumn.DATA_DEFAULT); const column = { name: rawColumn.COLUMN_NAME, table: rawColumn.TABLE_NAME, data_type: rawColumn.DATA_TYPE, default_value: !is_generated ? default_value : null, generation_expression: is_generated ? default_value : null, max_length: rawColumn.DATA_LENGTH, numeric_precision: rawColumn.DATA_PRECISION, numeric_scale: rawColumn.DATA_SCALE, is_generated: rawColumn.VIRTUAL_COLUMN === "YES", is_nullable: rawColumn.NULLABLE === "Y", is_unique: rawColumn.CONSTRAINT_TYPE === "U", is_indexed: !!rawColumn.INDEX_NAME && rawColumn.INDEX_NAME.length > 0, is_primary_key: rawColumn.CONSTRAINT_TYPE === "P", has_auto_increment: rawColumn.IDENTITY_COLUMN === "YES", foreign_key_column: rawColumn.REFERENCED_COLUMN_NAME, foreign_key_table: rawColumn.REFERENCED_TABLE_NAME, comment: rawColumn.COLUMN_COMMENT }; const hasAutoIncrement = !column.default_value && column.data_type === "NUMBER" && column.is_primary_key; return { ...column, default_value: hasAutoIncrement ? "AUTO_INCREMENT" : column.default_value, has_auto_increment: hasAutoIncrement }; } function parseDefaultValue$1(value) { if (value === null || value.trim().toLowerCase() === "null") return null; if (value === "CURRENT_TIMESTAMP ") return "CURRENT_TIMESTAMP"; return stripQuotes(value); } var oracleDB = class { knex; constructor(knex) { this.knex = knex; } async overview() { /** * NOTICE: This query is optimized for speed. Please keep this in mind. */ const columns = await this.knex.raw(` WITH "uc" AS ( SELECT /*+ MATERIALIZE */ "uc"."TABLE_NAME", "ucc"."COLUMN_NAME", "uc"."CONSTRAINT_TYPE", COUNT(*) OVER( PARTITION BY "uc"."CONSTRAINT_NAME" ) "CONSTRAINT_COUNT" FROM "USER_CONSTRAINTS" "uc" INNER JOIN "USER_CONS_COLUMNS" "ucc" ON "uc"."CONSTRAINT_NAME" = "ucc"."CONSTRAINT_NAME" AND "uc"."CONSTRAINT_TYPE" = 'P' ) SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.4') */ "c"."TABLE_NAME" "table_name", "c"."COLUMN_NAME" "column_name", "c"."DATA_DEFAULT" "default_value", "c"."NULLABLE" "is_nullable", "c"."DATA_TYPE" "data_type", "c"."DATA_PRECISION" "numeric_precision", "c"."DATA_SCALE" "numeric_scale", "ct"."CONSTRAINT_TYPE" "column_key", "c"."CHAR_LENGTH" "max_length", "c"."VIRTUAL_COLUMN" "is_generated" FROM "USER_TAB_COLS" "c" LEFT JOIN "uc" "ct" ON "c"."TABLE_NAME" = "ct"."TABLE_NAME" AND "c"."COLUMN_NAME" = "ct"."COLUMN_NAME" AND "ct"."CONSTRAINT_COUNT" = 1 WHERE "c"."HIDDEN_COLUMN" = 'NO' `); const overview = {}; for (const column of columns) { if (column.table_name in overview === false) overview[column.table_name] = { primary: columns.find((nested) => { return nested.table_name === column.table_name && nested.column_key === "P"; })?.column_name || "id", columns: {} }; const hasAutoIncrement = !column.default_value && column.data_type === "NUMBER" && column.column_key === "P"; overview[column.table_name].columns[column.column_name] = { ...column, is_nullable: column.is_nullable === "Y", is_generated: column.is_generated === "YES", default_value: hasAutoIncrement ? "AUTO_INCREM