diff --git a/dist/lib/schema/get-table-columns-gemnini.d.ts b/dist/lib/schema/get-table-columns-gemnini.d.ts new file mode 100644 index 0000000..d0fb6ae --- /dev/null +++ b/dist/lib/schema/get-table-columns-gemnini.d.ts @@ -0,0 +1,13 @@ +import type { BunMariaDBConfig } from "../../types"; +export type ColumnInfoRow = { + name: string; + type: string; + comment?: string; + isNullable: boolean; + columnDefault: string | null; + extra: string; +}; +export default function getTableColumnsGemini({ tableName, config, }: { + tableName: string; + config?: BunMariaDBConfig; +}): Promise; diff --git a/dist/lib/schema/get-table-columns-gemnini.js b/dist/lib/schema/get-table-columns-gemnini.js new file mode 100644 index 0000000..d4088a4 --- /dev/null +++ b/dist/lib/schema/get-table-columns-gemnini.js @@ -0,0 +1,45 @@ +import { querySchemaRows } from "./run-schema-query"; +import schemaCondition from "./schema-condition"; +export default async function getTableColumnsGemini({ tableName, config, }) { + const schemaCond = schemaCondition(config); + const rows = await querySchemaRows({ + query: ` + SELECT + c.COLUMN_NAME, + c.COLUMN_TYPE, + c.COLUMN_COMMENT, + c.IS_NULLABLE, + c.COLUMN_DEFAULT, + c.EXTRA, + EXISTS ( + SELECT 1 + FROM information_schema.CHECK_CONSTRAINTS cc + WHERE cc.CONSTRAINT_SCHEMA = c.TABLE_SCHEMA + AND cc.TABLE_NAME = c.TABLE_NAME + AND cc.CHECK_CLAUSE LIKE CONCAT('%json_valid(\`', c.COLUMN_NAME, '\`)%') + ) AS IS_JSON + FROM information_schema.COLUMNS c + WHERE ${schemaCond.where.replace(/\bTABLE_SCHEMA\b/g, "c.TABLE_SCHEMA")} + AND c.TABLE_NAME = ? + ORDER BY c.ORDINAL_POSITION + `, + values: [...schemaCond.values, tableName], + config, + }); + return rows.map((row) => { + const isJson = Number(row.IS_JSON) === 1; + // Normalize longtext with a json_valid constraint to "json" + let resolvedType = row.COLUMN_TYPE; + if (isJson && row.COLUMN_TYPE.toLowerCase() === "longtext") { + resolvedType = "json"; + } + return { + name: row.COLUMN_NAME, + type: resolvedType, + comment: row.COLUMN_COMMENT, + isNullable: String(row.IS_NULLABLE).toUpperCase() === "YES", + columnDefault: row.COLUMN_DEFAULT, + extra: row.EXTRA || "", + }; + }); +} diff --git a/dist/lib/schema/map-data-types.js b/dist/lib/schema/map-data-types.js index 78513d3..a50ba84 100644 --- a/dist/lib/schema/map-data-types.js +++ b/dist/lib/schema/map-data-types.js @@ -66,11 +66,12 @@ export default function mapDataType(field) { case "YEAR": return "YEAR"; case "UUID": - return "CHAR(36)"; // MariaDB does not have a native UUID type + return "UUID"; case "JSON": return "JSON"; case "INET6": return "INET6"; + case "BOOL": case "BOOLEAN": return "TINYINT(1)"; case "ENUM": { diff --git a/dist/lib/schema/update-table.js b/dist/lib/schema/update-table.js index 2bb8cc6..653901c 100644 --- a/dist/lib/schema/update-table.js +++ b/dist/lib/schema/update-table.js @@ -1,6 +1,7 @@ import buildColumnDefinition, { fieldRequiresNotNull, } from "./build-column-definition"; import createTable from "./create-table"; import getTableColumns, {} from "./get-table-columns"; +import getTableColumnsGemini from "./get-table-columns-gemnini"; import isVectorField from "./is-vector-field"; import mapDataType from "./map-data-types"; import MariaDBQuoteGen from "./mariadb-quote-gen"; @@ -110,7 +111,9 @@ function columnAttributesDiverged(live, field) { if (wantsOnUpdate && hasOnUpdate) { const liveOnUpdate = normalizeDefault((live.extra.match(/on update\s+(.+)/i) || [])[1] || ""); const expectedOu = normalizeDefault(wantsOnUpdate); - if (liveOnUpdate && expectedOu && !defaultsMatch(liveOnUpdate, expectedOu)) { + if (liveOnUpdate && + expectedOu && + !defaultsMatch(liveOnUpdate, expectedOu)) { return true; } } @@ -185,7 +188,7 @@ async function recreateColumn({ tableName, field, config, }) { await addColumn({ tableName, field, config }); } export default async function updateTable({ table, config, }) { - const existingColumns = await getTableColumns({ + const existingColumns = await getTableColumnsGemini({ tableName: table.tableName, config, }); @@ -207,7 +210,8 @@ export default async function updateTable({ table, config, }) { fieldsToAdd.push(field); } else { - let typeDiverged = !columnTypesMatch(liveField.type, mapDataType(field)); + let mapped_data_type = mapDataType(field); + let typeDiverged = !columnTypesMatch(liveField.type, mapped_data_type); if (isVectorField(field)) { typeDiverged = vectorTypeDiverged(liveField.type, liveField.comment || "", field); if (typeDiverged) { diff --git a/package.json b/package.json index 25648bd..5180160 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@moduletrace/bun-mariadb", - "version": "1.0.12", + "version": "1.0.13", "description": "Schema-driven MariaDB manager for Bun", "author": "Benjamin Toby", "license": "MIT", diff --git a/src/lib/schema/get-table-columns-gemnini.ts b/src/lib/schema/get-table-columns-gemnini.ts new file mode 100644 index 0000000..dedbc03 --- /dev/null +++ b/src/lib/schema/get-table-columns-gemnini.ts @@ -0,0 +1,73 @@ +import type { BunMariaDBConfig } from "../../types"; +import { querySchemaRows } from "./run-schema-query"; +import schemaCondition from "./schema-condition"; + +export type ColumnInfoRow = { + name: string; + type: string; + comment?: string; + isNullable: boolean; + columnDefault: string | null; + extra: string; +}; + +export default async function getTableColumnsGemini({ + tableName, + config, +}: { + tableName: string; + config?: BunMariaDBConfig; +}): Promise { + const schemaCond = schemaCondition(config); + const rows = await querySchemaRows<{ + COLUMN_NAME: string; + COLUMN_TYPE: string; + COLUMN_COMMENT: string; + IS_NULLABLE: string; + COLUMN_DEFAULT: string | null; + EXTRA: string; + IS_JSON: number | boolean; + }>({ + query: ` + SELECT + c.COLUMN_NAME, + c.COLUMN_TYPE, + c.COLUMN_COMMENT, + c.IS_NULLABLE, + c.COLUMN_DEFAULT, + c.EXTRA, + EXISTS ( + SELECT 1 + FROM information_schema.CHECK_CONSTRAINTS cc + WHERE cc.CONSTRAINT_SCHEMA = c.TABLE_SCHEMA + AND cc.TABLE_NAME = c.TABLE_NAME + AND cc.CHECK_CLAUSE LIKE CONCAT('%json_valid(\`', c.COLUMN_NAME, '\`)%') + ) AS IS_JSON + FROM information_schema.COLUMNS c + WHERE ${schemaCond.where.replace(/\bTABLE_SCHEMA\b/g, "c.TABLE_SCHEMA")} + AND c.TABLE_NAME = ? + ORDER BY c.ORDINAL_POSITION + `, + values: [...schemaCond.values, tableName], + config, + }); + + return rows.map((row) => { + const isJson = Number(row.IS_JSON) === 1; + + // Normalize longtext with a json_valid constraint to "json" + let resolvedType = row.COLUMN_TYPE; + if (isJson && row.COLUMN_TYPE.toLowerCase() === "longtext") { + resolvedType = "json"; + } + + return { + name: row.COLUMN_NAME, + type: resolvedType, + comment: row.COLUMN_COMMENT, + isNullable: String(row.IS_NULLABLE).toUpperCase() === "YES", + columnDefault: row.COLUMN_DEFAULT, + extra: row.EXTRA || "", + }; + }); +} diff --git a/src/lib/schema/map-data-types.ts b/src/lib/schema/map-data-types.ts index f4cabdb..3d586d2 100644 --- a/src/lib/schema/map-data-types.ts +++ b/src/lib/schema/map-data-types.ts @@ -72,11 +72,12 @@ export default function mapDataType( case "YEAR": return "YEAR"; case "UUID": - return "CHAR(36)"; // MariaDB does not have a native UUID type + return "UUID"; case "JSON": return "JSON"; case "INET6": return "INET6"; + case "BOOL": case "BOOLEAN": return "TINYINT(1)"; case "ENUM": { diff --git a/src/lib/schema/update-table.ts b/src/lib/schema/update-table.ts index b263a7d..84169ec 100644 --- a/src/lib/schema/update-table.ts +++ b/src/lib/schema/update-table.ts @@ -8,6 +8,7 @@ import buildColumnDefinition, { } from "./build-column-definition"; import createTable from "./create-table"; import getTableColumns, { type ColumnInfoRow } from "./get-table-columns"; +import getTableColumnsGemini from "./get-table-columns-gemnini"; import isVectorField from "./is-vector-field"; import mapDataType from "./map-data-types"; import MariaDBQuoteGen from "./mariadb-quote-gen"; @@ -137,7 +138,11 @@ function columnAttributesDiverged( (live.extra.match(/on update\s+(.+)/i) || [])[1] || "", ); const expectedOu = normalizeDefault(wantsOnUpdate); - if (liveOnUpdate && expectedOu && !defaultsMatch(liveOnUpdate, expectedOu)) { + if ( + liveOnUpdate && + expectedOu && + !defaultsMatch(liveOnUpdate, expectedOu) + ) { return true; } } @@ -273,7 +278,7 @@ export default async function updateTable({ table: BUN_MARIADB_TableSchemaType; config?: BunMariaDBConfig; }): Promise { - const existingColumns = await getTableColumns({ + const existingColumns = await getTableColumnsGemini({ tableName: table.tableName, config, }); @@ -303,9 +308,11 @@ export default async function updateTable({ if (!liveField) { fieldsToAdd.push(field); } else { + let mapped_data_type = mapDataType(field); + let typeDiverged = !columnTypesMatch( liveField.type, - mapDataType(field), + mapped_data_type, ); if (isVectorField(field)) {