@mlagie/sql-connector 3.0.3 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,205 @@
1
+ const { Dialect } = require("./Dialect");
2
+ const { normalizeType } = require("../utils");
3
+
4
+ class MySQLDialect extends Dialect {
5
+ constructor() {
6
+ super("mysql", {
7
+ quote: "`",
8
+ capabilities: {
9
+ transactionalDDL: false,
10
+ renameColumn: true,
11
+ alterColumn: true
12
+ }
13
+ });
14
+ }
15
+
16
+ placeholder() {
17
+ return "?";
18
+ }
19
+
20
+ execute(connection, sql, values = []) {
21
+ return connection.promise ? connection.promise().query(sql, values).then(([rows]) => rows)
22
+ : new Promise((resolve, reject) => {
23
+ connection.query(sql, values, (error, rows) => error ? reject(error) : resolve(rows));
24
+ });
25
+ }
26
+
27
+ mapType(field) {
28
+ const rawType = normalizeType(field);
29
+ const type = String(rawType || "").toUpperCase();
30
+ const length = field.length || 255;
31
+
32
+ switch (type) {
33
+ case "STRING":
34
+ case "VARCHAR":
35
+ return `VARCHAR(${length})`;
36
+ case "CHAR":
37
+ return `CHAR(${length})`;
38
+ case "TEXT":
39
+ return "TEXT";
40
+ case "NUMBER":
41
+ case "INT":
42
+ case "INTEGER":
43
+ return "INT";
44
+ case "SMALLINT":
45
+ return "SMALLINT";
46
+ case "MEDIUMINT":
47
+ return "MEDIUMINT";
48
+ case "BIGINT":
49
+ return "BIGINT";
50
+ case "DECIMAL":
51
+ case "NUMERIC":
52
+ return "DECIMAL";
53
+ case "FLOAT":
54
+ return "DOUBLE";
55
+ case "DOUBLE":
56
+ return "DOUBLE";
57
+ case "BOOLEAN":
58
+ case "BOOL":
59
+ case "TINYINT":
60
+ return "BOOLEAN";
61
+ case "DATE":
62
+ return "DATE";
63
+ case "DATETIME":
64
+ case "TIMESTAMP":
65
+ case "NOW":
66
+ case "CURRENTTIMESTAMP":
67
+ return "DATETIME";
68
+ case "OBJECT":
69
+ case "JSON":
70
+ return "JSON";
71
+ case "BLOB":
72
+ return "BLOB";
73
+ case "VARBINARY":
74
+ case "BINARY":
75
+ return "VARBINARY";
76
+ default:
77
+ throw new Error(`Unsupported MySQL type: ${rawType}`);
78
+ }
79
+ }
80
+
81
+ defaultSql(value) {
82
+ if (value === undefined) return "";
83
+ if (value === null) return "DEFAULT NULL";
84
+ if (typeof value === "number") return `DEFAULT ${value}`;
85
+ if (typeof value === "boolean") return `DEFAULT ${value ? 1 : 0}`;
86
+ if (typeof value === "string") {
87
+ if (/^(CURRENT_TIMESTAMP|CURRENT_DATE|CURRENT_TIME)\b/i.test(value)) {
88
+ return `DEFAULT ${value}`;
89
+ }
90
+ return `DEFAULT '${value.replace(/'/g, "''")}'`;
91
+ }
92
+ return `DEFAULT '${String(value).replace(/'/g, "''")}'`;
93
+ }
94
+
95
+ columnDefinition(name, field) {
96
+ if (field.primary_key && field.unique) {
97
+ throw new Error(`Field '${name}' cannot be both PRIMARY KEY and UNIQUE.`);
98
+ }
99
+
100
+ let sql = `${this.escape(name)} ${this.mapType(field)}`;
101
+
102
+ if (field.required || field.primary_key) sql += " NOT NULL";
103
+ if (field.default !== undefined) sql += ` ${this.defaultSql(field.default, field)}`;
104
+ if (field.unique) sql += " UNIQUE";
105
+ if (field.auto_increment) sql += " AUTO_INCREMENT";
106
+ if (field.primary_key) sql += " PRIMARY KEY";
107
+ if (typeof field.customize === "string" && field.customize.trim()) {
108
+ sql += ` ${field.customize.trim()}`;
109
+ }
110
+
111
+ return sql;
112
+ }
113
+
114
+ createTable(table, schema) {
115
+ const columns = [];
116
+ const foreignKeys = [];
117
+
118
+ for (const [name, field] of Object.entries(schema)) {
119
+ columns.push(this.columnDefinition(name, field));
120
+
121
+ if (field.foreignKey) {
122
+ const match = /^([A-Za-z_][A-Za-z0-9_$]*)(?:\s*\(\s*([A-Za-z_][A-Za-z0-9_$]*)\s*\)|\s*\.\s*([A-Za-z_][A-Za-z0-9_$]*)\s*)$/.exec(field.foreignKey);
123
+ if (!match) throw new Error(`Invalid foreign key definition for ${name}.`);
124
+ const refColumn = match[2] || match[3];
125
+ foreignKeys.push(
126
+ `FOREIGN KEY (${this.escape(name)}) REFERENCES ${this.escape(match[1])} (${this.escape(refColumn)})`
127
+ );
128
+ }
129
+ }
130
+
131
+ return `CREATE TABLE IF NOT EXISTS ${this.escape(table)} (${columns.concat(foreignKeys).join(", ")});`;
132
+ }
133
+
134
+ alterColumn(table, name, field) {
135
+ return `ALTER TABLE ${this.escape(table)} MODIFY COLUMN ${this.columnDefinition(name, field)};`;
136
+ }
137
+
138
+ introspectionQueries() {
139
+ return {
140
+ tables: `
141
+ SELECT TABLE_NAME AS table_name
142
+ FROM information_schema.tables
143
+ WHERE table_schema = DATABASE()
144
+ AND table_type = 'BASE TABLE'
145
+ `,
146
+ columns: `
147
+ SELECT
148
+ COLUMN_NAME AS column_name,
149
+ DATA_TYPE AS data_type,
150
+ COLUMN_TYPE AS column_type,
151
+ IS_NULLABLE AS is_nullable,
152
+ COLUMN_DEFAULT AS column_default,
153
+ COLUMN_KEY AS column_key,
154
+ EXTRA AS extra,
155
+ CHARACTER_MAXIMUM_LENGTH AS character_maximum_length
156
+ FROM information_schema.columns
157
+ WHERE table_schema = DATABASE()
158
+ AND table_name = ?
159
+ ORDER BY ORDINAL_POSITION
160
+ `
161
+ };
162
+ }
163
+
164
+ normalizeDatabaseColumn(row) {
165
+ const type = String(row.data_type || "").toLowerCase();
166
+ return {
167
+ type,
168
+ length: row.character_maximum_length ?? undefined,
169
+ required: row.is_nullable === "NO",
170
+ primary_key: row.column_key === "PRI",
171
+ unique: row.column_key === "UNI",
172
+ auto_increment: String(row.extra || "").toLowerCase().includes("auto_increment"),
173
+ default: row.column_default
174
+ };
175
+ }
176
+
177
+ createMigrationTable() {
178
+ return `
179
+ CREATE TABLE IF NOT EXISTS ${this.escape("sql_connector_migrations")} (
180
+ ${this.escape("id")} VARCHAR(191) NOT NULL PRIMARY KEY,
181
+ ${this.escape("checksum")} VARCHAR(64) NOT NULL,
182
+ ${this.escape("applied_at")} DATETIME NOT NULL,
183
+ ${this.escape("created_at")} DATETIME NOT NULL
184
+ );
185
+ `;
186
+ }
187
+
188
+ insertMigration() {
189
+ return `
190
+ INSERT INTO ${this.escape("sql_connector_migrations")}
191
+ (${this.escape("id")}, ${this.escape("checksum")}, ${this.escape("applied_at")}, ${this.escape("created_at")})
192
+ VALUES (?, ?, ?, ?)
193
+ `;
194
+ }
195
+
196
+ selectMigrations() {
197
+ return `SELECT ${this.escape("id")} AS id, ${this.escape("checksum")} AS checksum, ${this.escape("applied_at")} AS applied_at FROM ${this.escape("sql_connector_migrations")} ORDER BY ${this.escape("id")}`;
198
+ }
199
+
200
+ deleteMigration() {
201
+ return `DELETE FROM ${this.escape("sql_connector_migrations")} WHERE ${this.escape("id")} = ?`;
202
+ }
203
+ }
204
+
205
+ module.exports = { MySQLDialect };
@@ -0,0 +1,233 @@
1
+ const { Dialect } = require("./Dialect");
2
+ const { normalizeType } = require("../utils");
3
+
4
+ class PostgreSQLDialect extends Dialect {
5
+ constructor() {
6
+ super("postgres", {
7
+ quote: '"',
8
+ capabilities: {
9
+ transactionalDDL: true,
10
+ renameColumn: true,
11
+ alterColumn: true
12
+ }
13
+ });
14
+ }
15
+
16
+ placeholder(index) {
17
+ return `$${index + 1}`;
18
+ }
19
+
20
+ execute(connection, sql, values = []) {
21
+ if (typeof connection.query === "function") {
22
+ return connection.query(sql, values).then(result => result.rows);
23
+ }
24
+ throw new Error("PostgreSQL connection does not expose query().");
25
+ }
26
+
27
+ mapType(field) {
28
+ const rawType = normalizeType(field);
29
+ const type = String(rawType || "").toUpperCase();
30
+ const length = field.length || 255;
31
+
32
+ switch (type) {
33
+ case "STRING":
34
+ case "VARCHAR":
35
+ case "CHARACTER VARYING":
36
+ return `VARCHAR(${length})`;
37
+ case "CHAR":
38
+ case "BPCHAR":
39
+ case "CHARACTER":
40
+ return `CHAR(${length})`;
41
+ case "TEXT":
42
+ return "TEXT";
43
+ case "NUMBER":
44
+ case "INT":
45
+ case "INT4":
46
+ case "INTEGER":
47
+ return "INTEGER";
48
+ case "SMALLINT":
49
+ case "INT2":
50
+ return "SMALLINT";
51
+ case "BIGINT":
52
+ case "INT8":
53
+ return "BIGINT";
54
+ case "DECIMAL":
55
+ case "NUMERIC":
56
+ return "DECIMAL";
57
+ case "FLOAT":
58
+ case "FLOAT4":
59
+ case "REAL":
60
+ return "REAL";
61
+ case "DOUBLE":
62
+ case "FLOAT8":
63
+ case "DOUBLE PRECISION":
64
+ return "DOUBLE PRECISION";
65
+ case "BOOLEAN":
66
+ case "BOOL":
67
+ return "BOOLEAN";
68
+ case "DATE":
69
+ return "DATE";
70
+ case "DATETIME":
71
+ case "TIMESTAMP":
72
+ case "NOW":
73
+ case "CURRENTTIMESTAMP":
74
+ return "TIMESTAMP";
75
+ case "TIMESTAMPTZ":
76
+ case "TIMESTAMP WITH TIME ZONE":
77
+ return "TIMESTAMPTZ";
78
+ case "OBJECT":
79
+ case "JSON":
80
+ case "JSONB":
81
+ return "JSONB";
82
+ case "UUID":
83
+ return "UUID";
84
+ default:
85
+ throw new Error(`Unsupported PostgreSQL type: ${rawType}`);
86
+ }
87
+ }
88
+
89
+ defaultSql(value) {
90
+ if (value === undefined) return "";
91
+ if (value === null) return "DEFAULT NULL";
92
+ if (typeof value === "number") return `DEFAULT ${value}`;
93
+ if (typeof value === "boolean") return `DEFAULT ${value ? "TRUE" : "FALSE"}`;
94
+ if (typeof value === "string") {
95
+ if (/^(CURRENT_TIMESTAMP|CURRENT_DATE|CURRENT_TIME)\b/i.test(value)) {
96
+ return `DEFAULT ${value}`;
97
+ }
98
+ return `DEFAULT '${value.replace(/'/g, "''")}'`;
99
+ }
100
+ return `DEFAULT '${String(value).replace(/'/g, "''")}'`;
101
+ }
102
+
103
+ columnDefinition(name, field) {
104
+ if (field.primary_key && field.unique) {
105
+ throw new Error(`Field '${name}' cannot be both PRIMARY KEY and UNIQUE.`);
106
+ }
107
+
108
+ let sql = `${this.escape(name)} ${this.mapType(field)}`;
109
+
110
+ if (field.required || field.primary_key) sql += " NOT NULL";
111
+ if (field.default !== undefined) sql += ` ${this.defaultSql(field.default)}`;
112
+ if (field.unique) sql += " UNIQUE";
113
+ if (field.primary_key) sql += " PRIMARY KEY";
114
+ if (field.auto_increment) {
115
+ // Explicit auto-increment is represented with identity syntax.
116
+ sql = sql.replace(/\s+(INTEGER|BIGINT|SMALLINT)(?=\s|$)/i, " $1 GENERATED BY DEFAULT AS IDENTITY");
117
+ }
118
+ if (typeof field.customize === "string" && field.customize.trim()) {
119
+ sql += ` ${field.customize.trim()}`;
120
+ }
121
+
122
+ return sql;
123
+ }
124
+
125
+ createTable(table, schema) {
126
+ const columns = [];
127
+ const foreignKeys = [];
128
+
129
+ for (const [name, field] of Object.entries(schema)) {
130
+ columns.push(this.columnDefinition(name, field));
131
+
132
+ if (field.foreignKey) {
133
+ const match = /^([A-Za-z_][A-Za-z0-9_$]*)(?:\s*\(\s*([A-Za-z_][A-Za-z0-9_$]*)\s*\)|\s*\.\s*([A-Za-z_][A-Za-z0-9_$]*)\s*)$/.exec(field.foreignKey);
134
+ if (!match) throw new Error(`Invalid foreign key definition for ${name}.`);
135
+ const refColumn = match[2] || match[3];
136
+ foreignKeys.push(
137
+ `FOREIGN KEY (${this.escape(name)}) REFERENCES ${this.escape(match[1])} (${this.escape(refColumn)})`
138
+ );
139
+ }
140
+ }
141
+
142
+ return `CREATE TABLE IF NOT EXISTS ${this.escape(table)} (${columns.concat(foreignKeys).join(", ")});`;
143
+ }
144
+
145
+ alterColumn(table, name, field) {
146
+ const clauses = [
147
+ `ALTER COLUMN ${this.escape(name)} TYPE ${this.mapType(field)}`
148
+ ];
149
+
150
+ if (field.required || field.primary_key) {
151
+ clauses.push(`ALTER COLUMN ${this.escape(name)} SET NOT NULL`);
152
+ } else {
153
+ clauses.push(`ALTER COLUMN ${this.escape(name)} DROP NOT NULL`);
154
+ }
155
+
156
+ if (field.default === undefined) {
157
+ clauses.push(`ALTER COLUMN ${this.escape(name)} DROP DEFAULT`);
158
+ } else {
159
+ clauses.push(`ALTER COLUMN ${this.escape(name)} SET ${this.defaultSql(field.default)}`);
160
+ }
161
+
162
+ return `ALTER TABLE ${this.escape(table)} ${clauses.join(", ")};`;
163
+ }
164
+
165
+ introspectionQueries() {
166
+ return {
167
+ tables: `
168
+ SELECT table_name
169
+ FROM information_schema.tables
170
+ WHERE table_schema = current_schema()
171
+ AND table_type = 'BASE TABLE'
172
+ `,
173
+ columns: `
174
+ SELECT
175
+ column_name,
176
+ data_type,
177
+ udt_name,
178
+ is_nullable,
179
+ column_default,
180
+ character_maximum_length
181
+ FROM information_schema.columns
182
+ WHERE table_schema = current_schema()
183
+ AND table_name = $1
184
+ ORDER BY ordinal_position
185
+ `
186
+ };
187
+ }
188
+
189
+ normalizeDatabaseColumn(row) {
190
+ const type = String(row.udt_name || row.data_type || "").toLowerCase();
191
+ const isSerial = typeof row.column_default === "string" &&
192
+ row.column_default.includes("nextval(");
193
+
194
+ return {
195
+ type,
196
+ length: row.character_maximum_length ?? undefined,
197
+ required: row.is_nullable === "NO",
198
+ primary_key: false,
199
+ unique: false,
200
+ auto_increment: isSerial,
201
+ default: row.column_default
202
+ };
203
+ }
204
+
205
+ createMigrationTable() {
206
+ return `
207
+ CREATE TABLE IF NOT EXISTS ${this.escape("sql_connector_migrations")} (
208
+ ${this.escape("id")} VARCHAR(255) NOT NULL PRIMARY KEY,
209
+ ${this.escape("checksum")} VARCHAR(64) NOT NULL,
210
+ ${this.escape("applied_at")} TIMESTAMPTZ NOT NULL,
211
+ ${this.escape("created_at")} TIMESTAMPTZ NOT NULL
212
+ );
213
+ `;
214
+ }
215
+
216
+ insertMigration() {
217
+ return `
218
+ INSERT INTO ${this.escape("sql_connector_migrations")}
219
+ (${this.escape("id")}, ${this.escape("checksum")}, ${this.escape("applied_at")}, ${this.escape("created_at")})
220
+ VALUES ($1, $2, $3, $4)
221
+ `;
222
+ }
223
+
224
+ selectMigrations() {
225
+ return `SELECT ${this.escape("id")} AS id, ${this.escape("checksum")} AS checksum, ${this.escape("applied_at")} AS applied_at FROM ${this.escape("sql_connector_migrations")} ORDER BY ${this.escape("id")}`;
226
+ }
227
+
228
+ deleteMigration() {
229
+ return `DELETE FROM ${this.escape("sql_connector_migrations")} WHERE ${this.escape("id")} = $1`;
230
+ }
231
+ }
232
+
233
+ module.exports = { PostgreSQLDialect };
@@ -0,0 +1,21 @@
1
+ const { MySQLDialect } = require("./MySQLDialect");
2
+ const { PostgreSQLDialect } = require("./PostgreSQLDialect");
3
+
4
+ function createDialect(name) {
5
+ switch (String(name || "mysql").toLowerCase()) {
6
+ case "mysql":
7
+ case "mariadb":
8
+ return new MySQLDialect();
9
+ case "postgres":
10
+ case "postgresql":
11
+ return new PostgreSQLDialect();
12
+ default:
13
+ throw new Error(`Unsupported migration dialect: ${name}`);
14
+ }
15
+ }
16
+
17
+ module.exports = {
18
+ createDialect,
19
+ MySQLDialect,
20
+ PostgreSQLDialect
21
+ };
@@ -0,0 +1,28 @@
1
+ class MigrationError extends Error {
2
+ constructor(message, code = "MIGRATION_ERROR", details = undefined) {
3
+ super(message);
4
+ this.name = "MigrationError";
5
+ this.code = code;
6
+ this.details = details;
7
+ }
8
+ }
9
+
10
+ class DestructiveMigrationError extends MigrationError {
11
+ constructor(message, details) {
12
+ super(message, "DESTRUCTIVE_MIGRATION", details);
13
+ this.name = "DestructiveMigrationError";
14
+ }
15
+ }
16
+
17
+ class MigrationChecksumError extends MigrationError {
18
+ constructor(message, details) {
19
+ super(message, "MIGRATION_CHECKSUM_MISMATCH", details);
20
+ this.name = "MigrationChecksumError";
21
+ }
22
+ }
23
+
24
+ module.exports = {
25
+ MigrationError,
26
+ DestructiveMigrationError,
27
+ MigrationChecksumError
28
+ };
@@ -0,0 +1,18 @@
1
+ const { Migration } = require("./Migration");
2
+ const { MigrationPlan } = require("./MigrationPlan");
3
+ const { MigrationStore } = require("./MigrationStore");
4
+ const { MigrationRunner } = require("./MigrationRunner");
5
+ const { SchemaDiffer } = require("./SchemaDiffer");
6
+ const { SchemaInspector } = require("./SchemaInspector");
7
+ const { MySQLDialect, PostgreSQLDialect } = require("./dialects");
8
+
9
+ module.exports = {
10
+ Migration,
11
+ MigrationPlan,
12
+ MigrationStore,
13
+ MigrationRunner,
14
+ SchemaDiffer,
15
+ SchemaInspector,
16
+ MySQLDialect,
17
+ PostgreSQLDialect
18
+ };
@@ -0,0 +1,89 @@
1
+ const crypto = require("crypto");
2
+ const { getSafe } = require("../utils/security/safe");
3
+
4
+ function assertIdentifier(value, label = "identifier") {
5
+ if (typeof value !== "string" || !/^[A-Za-z_][A-Za-z0-9_$]*$/.test(value)) {
6
+ throw new Error(`Invalid ${label}: ${value}`);
7
+ }
8
+ return value;
9
+ }
10
+
11
+ function quoteIdentifier(name, quote) {
12
+ assertIdentifier(name);
13
+ return `${quote}${name}${quote}`;
14
+ }
15
+
16
+ function normalizeType(field) {
17
+ const type = field && field.type;
18
+ if (typeof type === "function") return type.name;
19
+ if (type && typeof type === "object" && typeof type.name === "string") return type.name;
20
+ if (typeof type === "string") return type;
21
+ return undefined;
22
+ }
23
+
24
+ function normalizeDefault(value) {
25
+ if (value === undefined) return undefined;
26
+ if (value === null) return null;
27
+ if (typeof value === "string") return value;
28
+ if (typeof value === "number" || typeof value === "boolean") return value;
29
+ return String(value);
30
+ }
31
+
32
+ function stableStringify(value) {
33
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
34
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
35
+ return `{${Object.keys(value).sort().map(k => `${JSON.stringify(k)}:${getSafe(value, k)}`)}.join(",")}}`;
36
+ }
37
+
38
+ function sha256(value) {
39
+ return crypto.createHash("sha256").update(value).digest("hex");
40
+ }
41
+
42
+ function migrationId(name = "migration") {
43
+ const now = new Date();
44
+ const stamp = now.toISOString()
45
+ .replace(/[-:TZ.]/g, "")
46
+ .slice(0, 14);
47
+ const safeName = String(name)
48
+ .trim()
49
+ .replace(/[^A-Za-z0-9_-]+/g, "_")
50
+ .replace(/^_+|_+$/g, "")
51
+ .slice(0, 80) || "migration";
52
+ return `${stamp}_${safeName}`;
53
+ }
54
+
55
+ function parseDuration(value) {
56
+ if (value === null || value === false || value === undefined) return null;
57
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
58
+
59
+ const match = /^(\d+(?:\.\d+)?)(ms|s|m|h|d|w)$/i.exec(String(value).trim());
60
+ if (!match) throw new Error(`Invalid retention duration: ${value}`);
61
+
62
+ const amount = Number(match[1]);
63
+ const unit = match[2].toLowerCase();
64
+ const factors = {
65
+ ms: 1,
66
+ s: 1000,
67
+ m: 60_000,
68
+ h: 3_600_000,
69
+ d: 86_400_000,
70
+ w: 604_800_000
71
+ };
72
+ return amount * getSafe(factors, unit);
73
+ }
74
+
75
+ function clone(value) {
76
+ return JSON.parse(JSON.stringify(value));
77
+ }
78
+
79
+ module.exports = {
80
+ assertIdentifier,
81
+ quoteIdentifier,
82
+ normalizeType,
83
+ normalizeDefault,
84
+ stableStringify,
85
+ sha256,
86
+ migrationId,
87
+ parseDuration,
88
+ clone
89
+ };
@@ -5,6 +5,7 @@ const { ModelInstance } = require("./ModelInstance");
5
5
  const { buildSelect, buildQueryParts } = require("../utils/buildQuery/buildQuery");
6
6
  const { getSafe, setSafe } = require("../utils/security/safe");
7
7
  const { getDialect } = require("../db/dialects");
8
+ const LENGTH_TYPES = new Set(["VARCHAR", "CHAR", "BINARY", "VARBINARY"]);
8
9
 
9
10
  function getFieldType(field) {
10
11
  if (typeof field === "object") {
@@ -32,8 +33,12 @@ function getColumnDefinition(fieldName, field) {
32
33
  if (Array.isArray(field.enum) && field.enum.length > 0) {
33
34
  const enumValues = field.enum.map(v => `'${v.replace(/'/g, "''")}'`).join(", ");
34
35
  colDef = `ENUM(${enumValues})`;
36
+ } else if (type === "DECIMAL") {
37
+ const precision = field.precision > 0 ? field.precision : 10;
38
+ const scale = field.scale >= 0 ? field.scale : 0;
39
+ colDef = `DECIMAL(${precision}, ${scale})`;
35
40
  } else {
36
- const hasLength = type === "VARCHAR" || (type === "INT" && getDialect().name === "mysql");
41
+ const hasLength = LENGTH_TYPES.has(type) || (type === "INT" && getDialect().name === "mysql");
37
42
  colDef = `${type}${hasLength ? `(${field.length > 0 ? field.length : 255})` : ""}`;
38
43
  }
39
44
 
@@ -163,7 +168,8 @@ class Model {
163
168
  const type = getSafe(sqlTypeMap, fieldType);
164
169
 
165
170
  if (!type) throw new Error(`Field ${fieldName} has unsupported type ${field}`);
166
- if (type == "VARCHAR") return `${getDialect().escape(fieldName)} ${type}(${lengthDefault})`;
171
+ if (LENGTH_TYPES.has(type)) return `${getDialect().escape(fieldName)} ${type}(${lengthDefault})`;
172
+ if (type === "DECIMAL") return `${getDialect().escape(fieldName)} ${type}(10, 0)`;
167
173
  return `${getDialect().escape(fieldName)} ${type}`;
168
174
  });
169
175
  return `CREATE TABLE IF NOT EXISTS ${getDialect().escape(this.name)} (${columns.join(', ')}${foreignKey.length > 0 ? ", " + foreignKey.join(', ') : ""}) ${getDialect().tableSuffix};`;
@@ -1,22 +1,22 @@
1
- /**
2
- * Represents a database schema.
3
- *
4
- * @example
5
- * const transferSchema = new Schema({
6
- * token: {
7
- * type: String,
8
- * length: 50
9
- * },
10
- * mdp: {
11
- * type: String,
12
- * length: 15
13
- * }
14
- * });
15
- */
16
- class Schema {
17
- constructor(schemaDict) {
18
- this.schemaDict = schemaDict;
1
+ /**
2
+ * Represents a database schema.
3
+ *
4
+ * @example
5
+ * const transferSchema = new Schema({
6
+ * token: {
7
+ * type: String,
8
+ * length: 50
9
+ * },
10
+ * mdp: {
11
+ * type: String,
12
+ * length: 15
13
+ * }
14
+ * });
15
+ */
16
+ class Schema {
17
+ constructor(schemaDict) {
18
+ this.schemaDict = schemaDict;
19
+ }
19
20
  }
20
- }
21
21
 
22
- module.exports = { Schema }
22
+ module.exports = { Schema }