ether-code 0.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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +130 -0
  3. package/cli/compiler.js +298 -0
  4. package/cli/ether.js +532 -0
  5. package/cli/watcher.js +106 -0
  6. package/generators/css-generator.js +583 -0
  7. package/generators/graphql-generator.js +868 -0
  8. package/generators/html-generator.js +745 -0
  9. package/generators/js-generator.js +909 -0
  10. package/generators/node-generator.js +467 -0
  11. package/generators/php-generator.js +706 -0
  12. package/generators/python-generator.js +913 -0
  13. package/generators/react-generator.js +599 -0
  14. package/generators/ruby-generator.js +904 -0
  15. package/generators/sql-generator.js +988 -0
  16. package/generators/ts-generator.js +569 -0
  17. package/i18n/i18n-css.json +743 -0
  18. package/i18n/i18n-graphql.json +1531 -0
  19. package/i18n/i18n-html.json +572 -0
  20. package/i18n/i18n-js.json +2790 -0
  21. package/i18n/i18n-node.json +2442 -0
  22. package/i18n/i18n-php.json +4306 -0
  23. package/i18n/i18n-python.json +3080 -0
  24. package/i18n/i18n-react.json +1784 -0
  25. package/i18n/i18n-ruby.json +1858 -0
  26. package/i18n/i18n-sql.json +3466 -0
  27. package/i18n/i18n-ts.json +442 -0
  28. package/lexer/ether-lexer.js +728 -0
  29. package/lexer/tokens.js +292 -0
  30. package/package.json +45 -0
  31. package/parsers/ast-css.js +545 -0
  32. package/parsers/ast-graphql.js +424 -0
  33. package/parsers/ast-html.js +886 -0
  34. package/parsers/ast-js.js +750 -0
  35. package/parsers/ast-node.js +2440 -0
  36. package/parsers/ast-php.js +957 -0
  37. package/parsers/ast-react.js +580 -0
  38. package/parsers/ast-ruby.js +895 -0
  39. package/parsers/ast-ts.js +1352 -0
  40. package/parsers/css-parser.js +1981 -0
  41. package/parsers/graphql-parser.js +2011 -0
  42. package/parsers/html-parser.js +1181 -0
  43. package/parsers/js-parser.js +2564 -0
  44. package/parsers/node-parser.js +2644 -0
  45. package/parsers/php-parser.js +3037 -0
  46. package/parsers/react-parser.js +1035 -0
  47. package/parsers/ruby-parser.js +2680 -0
  48. package/parsers/ts-parser.js +3881 -0
@@ -0,0 +1,988 @@
1
+ const fs = require('fs')
2
+
3
+ class SQLGenerator {
4
+ constructor(i18nPath = null) {
5
+ this.i18n = null
6
+ this.indent = 0
7
+ this.output = ''
8
+ this.keywordMap = {}
9
+ this.functionMap = {}
10
+ this.typeMap = {}
11
+
12
+ if (i18nPath) {
13
+ this.loadI18n(i18nPath)
14
+ }
15
+ }
16
+
17
+ loadI18n(filePath) {
18
+ const content = fs.readFileSync(filePath, 'utf-8')
19
+ this.i18n = JSON.parse(content)
20
+ this.buildMaps()
21
+ }
22
+
23
+ buildMaps() {
24
+ this.keywordMap = {}
25
+ this.functionMap = {}
26
+ this.typeMap = {}
27
+
28
+ if (!this.i18n) return
29
+
30
+ const addToMap = (map, section) => {
31
+ if (!section) return
32
+ for (const [key, translations] of Object.entries(section)) {
33
+ if (translations && translations.fr && translations.sql) {
34
+ map[translations.fr.toLowerCase()] = translations.sql
35
+ }
36
+ }
37
+ }
38
+
39
+ for (const sectionName of Object.keys(this.i18n)) {
40
+ addToMap(this.keywordMap, this.i18n[sectionName])
41
+ }
42
+ }
43
+
44
+ translate(word) {
45
+ if (!word) return ''
46
+ const lower = word.toLowerCase()
47
+ return this.keywordMap[lower] || this.translateGeneric(word)
48
+ }
49
+
50
+ translateGeneric(word) {
51
+ const translations = {
52
+ 'sélectionner': 'SELECT',
53
+ 'depuis': 'FROM',
54
+ 'ou': 'WHERE',
55
+ 'et': 'AND',
56
+ 'ou logique': 'OR',
57
+ 'non': 'NOT',
58
+ 'comme': 'AS',
59
+ 'joindre': 'JOIN',
60
+ 'gauche': 'LEFT',
61
+ 'droite': 'RIGHT',
62
+ 'interne': 'INNER',
63
+ 'externe': 'OUTER',
64
+ 'sur': 'ON',
65
+ 'grouper par': 'GROUP BY',
66
+ 'trier par': 'ORDER BY',
67
+ 'ascendant': 'ASC',
68
+ 'descendant': 'DESC',
69
+ 'limite': 'LIMIT',
70
+ 'decalage': 'OFFSET',
71
+ 'avoir': 'HAVING',
72
+ 'distinct': 'DISTINCT',
73
+ 'tout': 'ALL',
74
+ 'union': 'UNION',
75
+ 'intersection': 'INTERSECT',
76
+ 'difference': 'EXCEPT',
77
+ 'insérer': 'INSERT',
78
+ 'dans': 'INTO',
79
+ 'valeurs': 'VALUES',
80
+ 'mettre a jour': 'UPDATE',
81
+ 'définir': 'SET',
82
+ 'supprimer': 'DELETE',
83
+ 'créer': 'CREATE',
84
+ 'table': 'TABLE',
85
+ 'base de données': 'DATABASE',
86
+ 'schema': 'SCHEMA',
87
+ 'index': 'INDEX',
88
+ 'vue': 'VIEW',
89
+ 'modifier': 'ALTER',
90
+ 'ajouter': 'ADD',
91
+ 'supprimer colonne': 'DROP',
92
+ 'renommer': 'RENAME',
93
+ 'vers': 'TO',
94
+ 'colonne': 'COLUMN',
95
+ 'contrainte': 'CONSTRAINT',
96
+ 'clé primaire': 'PRIMARY KEY',
97
+ 'clé etrangere': 'FOREIGN KEY',
98
+ 'références': 'REFERENCES',
99
+ 'unique': 'UNIQUE',
100
+ 'non nul': 'NOT NULL',
101
+ 'par défaut': 'DEFAULT',
102
+ 'verification': 'CHECK',
103
+ 'cascade': 'CASCADE',
104
+ 'définir null': 'SET NULL',
105
+ 'définir défaut': 'SET DEFAULT',
106
+ 'restreindre': 'RESTRICT',
107
+ 'pas action': 'NO ACTION',
108
+ 'avec': 'WITH',
109
+ 'si existe': 'IF EXISTS',
110
+ 'si non existe': 'IF NOT EXISTS',
111
+ 'temporaire': 'TEMPORARY',
112
+ 'existe': 'EXISTS',
113
+ 'dans liste': 'IN',
114
+ 'entre': 'BETWEEN',
115
+ 'similaire': 'LIKE',
116
+ 'ilike': 'ILIKE',
117
+ 'est nul': 'IS NULL',
118
+ 'est non nul': 'IS NOT NULL',
119
+ 'cas': 'CASE',
120
+ 'quand': 'WHEN',
121
+ 'alors': 'THEN',
122
+ 'sinon': 'ELSE',
123
+ 'fin': 'END',
124
+ 'coalescence': 'COALESCE',
125
+ 'si null': 'NULLIF',
126
+ 'convertir': 'CAST',
127
+ 'compter': 'COUNT',
128
+ 'somme': 'SUM',
129
+ 'moyenne': 'AVG',
130
+ 'minimum': 'MIN',
131
+ 'maximum': 'MAX',
132
+ 'maintenant': 'NOW()',
133
+ 'date actuelle': 'CURRENT_DATE',
134
+ 'heure actuelle': 'CURRENT_TIME',
135
+ 'horodatage actuel': 'CURRENT_TIMESTAMP',
136
+ 'extraire': 'EXTRACT',
137
+ 'année': 'YEAR',
138
+ 'mois': 'MONTH',
139
+ 'jour': 'DAY',
140
+ 'heure': 'HOUR',
141
+ 'minute': 'MINUTE',
142
+ 'seconde': 'SECOND',
143
+ 'concatener': 'CONCAT',
144
+ 'longueur': 'LENGTH',
145
+ 'majuscules': 'UPPER',
146
+ 'minuscules': 'LOWER',
147
+ 'supprimer espaces': 'TRIM',
148
+ 'sous chaîne': 'SUBSTRING',
149
+ 'remplacer': 'REPLACE',
150
+ 'position': 'POSITION',
151
+ 'arrondir': 'ROUND',
152
+ 'plancher': 'FLOOR',
153
+ 'plafond': 'CEIL',
154
+ 'absolu': 'ABS',
155
+ 'puissance': 'POWER',
156
+ 'racine carree': 'SQRT',
157
+ 'vrai': 'TRUE',
158
+ 'faux': 'FALSE',
159
+ 'nul': 'NULL',
160
+ 'entier': 'INTEGER',
161
+ 'petit entier': 'SMALLINT',
162
+ 'grand entier': 'BIGINT',
163
+ 'decimal': 'DECIMAL',
164
+ 'numerique': 'NUMERIC',
165
+ 'reel': 'REAL',
166
+ 'double précision': 'DOUBLE PRECISION',
167
+ 'texte': 'TEXT',
168
+ 'caractère variable': 'VARCHAR',
169
+ 'caractère': 'CHAR',
170
+ 'booléen': 'BOOLEAN',
171
+ 'type date': 'DATE',
172
+ 'temps': 'TIME',
173
+ 'horodatage': 'TIMESTAMP',
174
+ 'intervalle': 'INTERVAL',
175
+ 'json': 'JSON',
176
+ 'jsonb': 'JSONB',
177
+ 'uuid': 'UUID',
178
+ 'tableau': 'ARRAY',
179
+ 'serial': 'SERIAL',
180
+ 'grand serial': 'BIGSERIAL',
181
+ 'commencer': 'BEGIN',
182
+ 'valider': 'COMMIT',
183
+ 'annuler': 'ROLLBACK',
184
+ 'point sauvegarde': 'SAVEPOINT',
185
+ 'transaction': 'TRANSACTION',
186
+ 'accorder': 'GRANT',
187
+ 'revoquer': 'REVOKE',
188
+ 'sur table': 'ON',
189
+ 'type role': 'ROLE',
190
+ 'utilisateur': 'USER',
191
+ 'public': 'PUBLIC',
192
+ 'sélection': 'SELECT',
193
+ 'insertion': 'INSERT',
194
+ 'mise a jour': 'UPDATE',
195
+ 'suppression': 'DELETE',
196
+ 'tous privileges': 'ALL PRIVILEGES',
197
+ 'expliquer': 'EXPLAIN',
198
+ 'analyser': 'ANALYZE',
199
+ 'vide': 'VACUUM',
200
+ 'reindexer': 'REINDEX'
201
+ }
202
+
203
+ const lower = word.toLowerCase()
204
+ return translations[lower] || word
205
+ }
206
+
207
+ generate(ast) {
208
+ this.output = ''
209
+ this.indent = 0
210
+
211
+ if (Array.isArray(ast)) {
212
+ for (const node of ast) {
213
+ this.generateNode(node)
214
+ this.output += ';\n\n'
215
+ }
216
+ } else if (ast && ast.type) {
217
+ this.generateNode(ast)
218
+ this.output += ';'
219
+ } else if (ast && ast.statements) {
220
+ for (const stmt of ast.statements) {
221
+ this.generateNode(stmt)
222
+ this.output += ';\n\n'
223
+ }
224
+ }
225
+
226
+ return this.output.trim()
227
+ }
228
+
229
+ generateNode(node) {
230
+ if (!node) return ''
231
+
232
+ switch (node.type) {
233
+ case 'SelectStatement':
234
+ return this.generateSelect(node)
235
+ case 'InsertStatement':
236
+ return this.generateInsert(node)
237
+ case 'UpdateStatement':
238
+ return this.generateUpdate(node)
239
+ case 'DeleteStatement':
240
+ return this.generateDelete(node)
241
+ case 'CreateTableStatement':
242
+ return this.generateCreateTable(node)
243
+ case 'AlterTableStatement':
244
+ return this.generateAlterTable(node)
245
+ case 'DropStatement':
246
+ return this.generateDrop(node)
247
+ case 'CreateIndexStatement':
248
+ return this.generateCreateIndex(node)
249
+ case 'CreateViewStatement':
250
+ return this.generateCreateView(node)
251
+ case 'TransactionStatement':
252
+ return this.generateTransaction(node)
253
+ case 'GrantStatement':
254
+ return this.generateGrant(node)
255
+ case 'RevokeStatement':
256
+ return this.generateRevoke(node)
257
+ case 'WithStatement':
258
+ return this.generateWith(node)
259
+ case 'UnionStatement':
260
+ return this.generateUnion(node)
261
+ default:
262
+ return ''
263
+ }
264
+ }
265
+
266
+ generateSelect(node) {
267
+ let sql = 'SELECT'
268
+
269
+ if (node.distinct) {
270
+ sql += ' DISTINCT'
271
+ }
272
+
273
+ const columns = (node.columns || ['*']).map(c => this.generateColumn(c))
274
+ sql += ' ' + columns.join(', ')
275
+
276
+ if (node.from) {
277
+ sql += '\nFROM ' + this.generateFrom(node.from)
278
+ }
279
+
280
+ if (node.joins && node.joins.length > 0) {
281
+ for (const join of node.joins) {
282
+ sql += '\n' + this.generateJoin(join)
283
+ }
284
+ }
285
+
286
+ if (node.where) {
287
+ sql += '\nWHERE ' + this.generateCondition(node.where)
288
+ }
289
+
290
+ if (node.groupBy && node.groupBy.length > 0) {
291
+ sql += '\nGROUP BY ' + node.groupBy.map(g => this.generateExpression(g)).join(', ')
292
+ }
293
+
294
+ if (node.having) {
295
+ sql += '\nHAVING ' + this.generateCondition(node.having)
296
+ }
297
+
298
+ if (node.orderBy && node.orderBy.length > 0) {
299
+ sql += '\nORDER BY ' + node.orderBy.map(o => this.generateOrderBy(o)).join(', ')
300
+ }
301
+
302
+ if (node.limit !== undefined) {
303
+ sql += '\nLIMIT ' + node.limit
304
+ }
305
+
306
+ if (node.offset !== undefined) {
307
+ sql += ' OFFSET ' + node.offset
308
+ }
309
+
310
+ this.output += sql
311
+ return sql
312
+ }
313
+
314
+ generateColumn(col) {
315
+ if (typeof col === 'string') {
316
+ return this.translate(col)
317
+ }
318
+
319
+ let result = this.generateExpression(col.expression || col)
320
+
321
+ if (col.alias) {
322
+ result += ' AS ' + this.translate(col.alias)
323
+ }
324
+
325
+ return result
326
+ }
327
+
328
+ generateFrom(from) {
329
+ if (typeof from === 'string') {
330
+ return this.translate(from)
331
+ }
332
+
333
+ if (from.subquery) {
334
+ return '(' + this.generateSelect(from.subquery) + ')' +
335
+ (from.alias ? ' AS ' + from.alias : '')
336
+ }
337
+
338
+ let result = this.translate(from.table || from.name)
339
+ if (from.alias) {
340
+ result += ' AS ' + from.alias
341
+ }
342
+ return result
343
+ }
344
+
345
+ generateJoin(join) {
346
+ let type = ''
347
+ switch (join.type?.toLowerCase()) {
348
+ case 'gauche':
349
+ case 'left':
350
+ type = 'LEFT JOIN'
351
+ break
352
+ case 'droite':
353
+ case 'right':
354
+ type = 'RIGHT JOIN'
355
+ break
356
+ case 'interne':
357
+ case 'inner':
358
+ type = 'INNER JOIN'
359
+ break
360
+ case 'externe':
361
+ case 'outer':
362
+ case 'complet':
363
+ case 'full':
364
+ type = 'FULL OUTER JOIN'
365
+ break
366
+ case 'croise':
367
+ case 'cross':
368
+ type = 'CROSS JOIN'
369
+ break
370
+ default:
371
+ type = 'JOIN'
372
+ }
373
+
374
+ let result = type + ' ' + this.generateFrom(join.table)
375
+
376
+ if (join.on) {
377
+ result += ' ON ' + this.generateCondition(join.on)
378
+ }
379
+
380
+ if (join.using) {
381
+ result += ' USING (' + join.using.join(', ') + ')'
382
+ }
383
+
384
+ return result
385
+ }
386
+
387
+ generateCondition(cond) {
388
+ if (typeof cond === 'string') {
389
+ return cond
390
+ }
391
+
392
+ if (cond.and) {
393
+ return cond.and.map(c => '(' + this.generateCondition(c) + ')').join(' AND ')
394
+ }
395
+
396
+ if (cond.or) {
397
+ return cond.or.map(c => '(' + this.generateCondition(c) + ')').join(' OR ')
398
+ }
399
+
400
+ if (cond.not) {
401
+ return 'NOT (' + this.generateCondition(cond.not) + ')'
402
+ }
403
+
404
+ const left = this.generateExpression(cond.left || cond.column)
405
+ const op = this.translateOperator(cond.operator || cond.op)
406
+ const right = this.generateExpression(cond.right || cond.value)
407
+
408
+ if (op === 'IS NULL' || op === 'IS NOT NULL') {
409
+ return `${left} ${op}`
410
+ }
411
+
412
+ if (op === 'IN' || op === 'NOT IN') {
413
+ const values = Array.isArray(right) ? right.join(', ') : right
414
+ return `${left} ${op} (${values})`
415
+ }
416
+
417
+ if (op === 'BETWEEN') {
418
+ return `${left} BETWEEN ${this.generateExpression(cond.low)} AND ${this.generateExpression(cond.high)}`
419
+ }
420
+
421
+ return `${left} ${op} ${right}`
422
+ }
423
+
424
+ translateOperator(op) {
425
+ const operators = {
426
+ 'egal': '=',
427
+ 'different': '!=',
428
+ 'superieur': '>',
429
+ 'inferieur': '<',
430
+ 'superieur ou egal': '>=',
431
+ 'inferieur ou egal': '<=',
432
+ 'similaire': 'LIKE',
433
+ 'ilike': 'ILIKE',
434
+ 'dans': 'IN',
435
+ 'pas dans': 'NOT IN',
436
+ 'entre': 'BETWEEN',
437
+ 'est nul': 'IS NULL',
438
+ 'est non nul': 'IS NOT NULL',
439
+ 'contient': '@>',
440
+ 'contenu dans': '<@'
441
+ }
442
+ return operators[op?.toLowerCase()] || op
443
+ }
444
+
445
+ generateExpression(expr) {
446
+ if (expr === null || expr === undefined) return 'NULL'
447
+ if (typeof expr === 'number') return String(expr)
448
+ if (typeof expr === 'boolean') return expr ? 'TRUE' : 'FALSE'
449
+ if (typeof expr === 'string') {
450
+ if (expr.toLowerCase() === 'true') return 'TRUE'
451
+ if (expr.toLowerCase() === 'false') return 'FALSE'
452
+ if (expr.startsWith("'") || expr.startsWith('"')) return expr
453
+ if (/^\d+$/.test(expr)) return expr
454
+ return this.translate(expr)
455
+ }
456
+
457
+ if (expr.type === 'function' || expr.function) {
458
+ return this.generateFunction(expr)
459
+ }
460
+
461
+ if (expr.type === 'case') {
462
+ return this.generateCase(expr)
463
+ }
464
+
465
+ if (expr.type === 'subquery') {
466
+ return '(' + this.generateSelect(expr.query) + ')'
467
+ }
468
+
469
+ if (expr.literal !== undefined) {
470
+ return this.generateLiteral(expr.literal)
471
+ }
472
+
473
+ if (expr.column) {
474
+ let result = ''
475
+ if (expr.table) {
476
+ result = expr.table + '.'
477
+ }
478
+ result += this.translate(expr.column)
479
+ return result
480
+ }
481
+
482
+ return String(expr)
483
+ }
484
+
485
+ generateFunction(func) {
486
+ const name = this.translate(func.name || func.function)
487
+ const args = (func.arguments || func.args || []).map(a => this.generateExpression(a))
488
+
489
+ let result = name + '(' + args.join(', ') + ')'
490
+
491
+ if (func.over) {
492
+ result += ' OVER (' + this.generateWindow(func.over) + ')'
493
+ }
494
+
495
+ return result
496
+ }
497
+
498
+ generateWindow(window) {
499
+ let parts = []
500
+
501
+ if (window.partitionBy) {
502
+ parts.push('PARTITION BY ' + window.partitionBy.map(p => this.generateExpression(p)).join(', '))
503
+ }
504
+
505
+ if (window.orderBy) {
506
+ parts.push('ORDER BY ' + window.orderBy.map(o => this.generateOrderBy(o)).join(', '))
507
+ }
508
+
509
+ return parts.join(' ')
510
+ }
511
+
512
+ generateCase(caseExpr) {
513
+ let sql = 'CASE'
514
+
515
+ if (caseExpr.expression) {
516
+ sql += ' ' + this.generateExpression(caseExpr.expression)
517
+ }
518
+
519
+ for (const when of caseExpr.whens || []) {
520
+ sql += ' WHEN ' + this.generateCondition(when.condition)
521
+ sql += ' THEN ' + this.generateExpression(when.result)
522
+ }
523
+
524
+ if (caseExpr.else !== undefined) {
525
+ sql += ' ELSE ' + this.generateExpression(caseExpr.else)
526
+ }
527
+
528
+ sql += ' END'
529
+ return sql
530
+ }
531
+
532
+ generateOrderBy(order) {
533
+ let result = this.generateExpression(order.column || order.expression || order)
534
+
535
+ if (order.direction) {
536
+ const dir = order.direction.toLowerCase()
537
+ result += dir === 'desc' || dir === 'descendant' ? ' DESC' : ' ASC'
538
+ }
539
+
540
+ if (order.nulls) {
541
+ result += order.nulls === 'first' ? ' NULLS FIRST' : ' NULLS LAST'
542
+ }
543
+
544
+ return result
545
+ }
546
+
547
+ generateLiteral(value) {
548
+ if (value === null) return 'NULL'
549
+ if (typeof value === 'number') return String(value)
550
+ if (typeof value === 'boolean') return value ? 'TRUE' : 'FALSE'
551
+ if (typeof value === 'string') {
552
+ return "'" + value.replace(/'/g, "''") + "'"
553
+ }
554
+ if (Array.isArray(value)) {
555
+ return 'ARRAY[' + value.map(v => this.generateLiteral(v)).join(', ') + ']'
556
+ }
557
+ return String(value)
558
+ }
559
+
560
+ generateInsert(node) {
561
+ let sql = 'INSERT INTO ' + this.translate(node.table)
562
+
563
+ if (node.columns && node.columns.length > 0) {
564
+ sql += ' (' + node.columns.map(c => this.translate(c)).join(', ') + ')'
565
+ }
566
+
567
+ if (node.values) {
568
+ sql += '\nVALUES'
569
+ const rows = node.values.map(row => {
570
+ const vals = row.map(v => this.generateLiteral(v))
571
+ return '(' + vals.join(', ') + ')'
572
+ })
573
+ sql += ' ' + rows.join(',\n ')
574
+ }
575
+
576
+ if (node.select) {
577
+ sql += '\n' + this.generateSelect(node.select)
578
+ }
579
+
580
+ if (node.onConflict) {
581
+ sql += '\nON CONFLICT '
582
+ if (node.onConflict.columns) {
583
+ sql += '(' + node.onConflict.columns.join(', ') + ') '
584
+ }
585
+ if (node.onConflict.doNothing) {
586
+ sql += 'DO NOTHING'
587
+ } else if (node.onConflict.doUpdate) {
588
+ sql += 'DO UPDATE SET '
589
+ const updates = Object.entries(node.onConflict.doUpdate).map(([k, v]) =>
590
+ this.translate(k) + ' = ' + this.generateExpression(v)
591
+ )
592
+ sql += updates.join(', ')
593
+ }
594
+ }
595
+
596
+ if (node.returning) {
597
+ sql += '\nRETURNING ' + node.returning.map(c => this.generateColumn(c)).join(', ')
598
+ }
599
+
600
+ this.output += sql
601
+ return sql
602
+ }
603
+
604
+ generateUpdate(node) {
605
+ let sql = 'UPDATE ' + this.translate(node.table)
606
+
607
+ sql += '\nSET '
608
+ const sets = Object.entries(node.set || {}).map(([k, v]) =>
609
+ this.translate(k) + ' = ' + this.generateExpression(v)
610
+ )
611
+ sql += sets.join(',\n ')
612
+
613
+ if (node.from) {
614
+ sql += '\nFROM ' + this.generateFrom(node.from)
615
+ }
616
+
617
+ if (node.where) {
618
+ sql += '\nWHERE ' + this.generateCondition(node.where)
619
+ }
620
+
621
+ if (node.returning) {
622
+ sql += '\nRETURNING ' + node.returning.map(c => this.generateColumn(c)).join(', ')
623
+ }
624
+
625
+ this.output += sql
626
+ return sql
627
+ }
628
+
629
+ generateDelete(node) {
630
+ let sql = 'DELETE FROM ' + this.translate(node.table)
631
+
632
+ if (node.using) {
633
+ sql += '\nUSING ' + this.generateFrom(node.using)
634
+ }
635
+
636
+ if (node.where) {
637
+ sql += '\nWHERE ' + this.generateCondition(node.where)
638
+ }
639
+
640
+ if (node.returning) {
641
+ sql += '\nRETURNING ' + node.returning.map(c => this.generateColumn(c)).join(', ')
642
+ }
643
+
644
+ this.output += sql
645
+ return sql
646
+ }
647
+
648
+ generateCreateTable(node) {
649
+ let sql = 'CREATE'
650
+
651
+ if (node.temporary) sql += ' TEMPORARY'
652
+
653
+ sql += ' TABLE'
654
+
655
+ if (node.ifNotExists) sql += ' IF NOT EXISTS'
656
+
657
+ sql += ' ' + this.translate(node.table)
658
+ sql += ' (\n'
659
+
660
+ const columns = (node.columns || []).map(col => this.generateColumnDef(col))
661
+ const constraints = (node.constraints || []).map(c => this.generateConstraint(c))
662
+
663
+ sql += ' ' + [...columns, ...constraints].join(',\n ')
664
+ sql += '\n)'
665
+
666
+ this.output += sql
667
+ return sql
668
+ }
669
+
670
+ generateColumnDef(col) {
671
+ let def = this.translate(col.name) + ' ' + this.translate(col.type)
672
+
673
+ if (col.length) {
674
+ def += '(' + col.length + ')'
675
+ }
676
+
677
+ if (col.precision) {
678
+ def += '(' + col.precision + (col.scale ? ', ' + col.scale : '') + ')'
679
+ }
680
+
681
+ if (col.array) {
682
+ def += '[]'
683
+ }
684
+
685
+ if (col.primaryKey) {
686
+ def += ' PRIMARY KEY'
687
+ }
688
+
689
+ if (col.notNull || col.required) {
690
+ def += ' NOT NULL'
691
+ }
692
+
693
+ if (col.unique) {
694
+ def += ' UNIQUE'
695
+ }
696
+
697
+ if (col.default !== undefined) {
698
+ def += ' DEFAULT ' + this.generateExpression(col.default)
699
+ }
700
+
701
+ if (col.references) {
702
+ def += ' REFERENCES ' + col.references.table
703
+ if (col.references.column) {
704
+ def += '(' + col.references.column + ')'
705
+ }
706
+ if (col.references.onDelete) {
707
+ def += ' ON DELETE ' + col.references.onDelete.toUpperCase()
708
+ }
709
+ if (col.references.onUpdate) {
710
+ def += ' ON UPDATE ' + col.references.onUpdate.toUpperCase()
711
+ }
712
+ }
713
+
714
+ if (col.check) {
715
+ def += ' CHECK (' + this.generateCondition(col.check) + ')'
716
+ }
717
+
718
+ return def
719
+ }
720
+
721
+ generateConstraint(constraint) {
722
+ let sql = ''
723
+
724
+ if (constraint.name) {
725
+ sql += 'CONSTRAINT ' + constraint.name + ' '
726
+ }
727
+
728
+ switch (constraint.type?.toLowerCase()) {
729
+ case 'primary key':
730
+ case 'clé primaire':
731
+ sql += 'PRIMARY KEY (' + constraint.columns.join(', ') + ')'
732
+ break
733
+ case 'foreign key':
734
+ case 'clé etrangere':
735
+ sql += 'FOREIGN KEY (' + constraint.columns.join(', ') + ')'
736
+ sql += ' REFERENCES ' + constraint.references.table
737
+ sql += '(' + constraint.references.columns.join(', ') + ')'
738
+ if (constraint.references.onDelete) {
739
+ sql += ' ON DELETE ' + constraint.references.onDelete.toUpperCase()
740
+ }
741
+ break
742
+ case 'unique':
743
+ sql += 'UNIQUE (' + constraint.columns.join(', ') + ')'
744
+ break
745
+ case 'check':
746
+ case 'verification':
747
+ sql += 'CHECK (' + this.generateCondition(constraint.condition) + ')'
748
+ break
749
+ }
750
+
751
+ return sql
752
+ }
753
+
754
+ generateAlterTable(node) {
755
+ let sql = 'ALTER TABLE ' + this.translate(node.table)
756
+
757
+ const actions = (node.actions || [node.action]).map(action => {
758
+ switch (action.type?.toLowerCase()) {
759
+ case 'add':
760
+ case 'ajouter':
761
+ if (action.column) {
762
+ return 'ADD COLUMN ' + this.generateColumnDef(action.column)
763
+ }
764
+ if (action.constraint) {
765
+ return 'ADD ' + this.generateConstraint(action.constraint)
766
+ }
767
+ break
768
+ case 'drop':
769
+ case 'supprimer':
770
+ if (action.column) {
771
+ return 'DROP COLUMN ' + action.column
772
+ }
773
+ if (action.constraint) {
774
+ return 'DROP CONSTRAINT ' + action.constraint
775
+ }
776
+ break
777
+ case 'alter':
778
+ case 'modifier':
779
+ let alter = 'ALTER COLUMN ' + action.column
780
+ if (action.dataType || action.newType) {
781
+ alter += ' TYPE ' + this.translate(action.dataType || action.newType)
782
+ }
783
+ if (action.setDefault !== undefined) {
784
+ alter += ' SET DEFAULT ' + this.generateExpression(action.setDefault)
785
+ }
786
+ if (action.dropDefault) {
787
+ alter += ' DROP DEFAULT'
788
+ }
789
+ if (action.setNotNull) {
790
+ alter += ' SET NOT NULL'
791
+ }
792
+ if (action.dropNotNull) {
793
+ alter += ' DROP NOT NULL'
794
+ }
795
+ return alter
796
+ case 'rename':
797
+ case 'renommer':
798
+ if (action.column) {
799
+ return 'RENAME COLUMN ' + action.column + ' TO ' + action.to
800
+ }
801
+ return 'RENAME TO ' + action.to
802
+ }
803
+ return ''
804
+ })
805
+
806
+ sql += '\n ' + actions.filter(a => a).join(',\n ')
807
+
808
+ this.output += sql
809
+ return sql
810
+ }
811
+
812
+ generateDrop(node) {
813
+ let sql = 'DROP'
814
+
815
+ sql += ' ' + (node.objectType || 'TABLE').toUpperCase()
816
+
817
+ if (node.ifExists) {
818
+ sql += ' IF EXISTS'
819
+ }
820
+
821
+ sql += ' ' + this.translate(node.name)
822
+
823
+ if (node.cascade) {
824
+ sql += ' CASCADE'
825
+ }
826
+
827
+ this.output += sql
828
+ return sql
829
+ }
830
+
831
+ generateCreateIndex(node) {
832
+ let sql = 'CREATE'
833
+
834
+ if (node.unique) sql += ' UNIQUE'
835
+
836
+ sql += ' INDEX'
837
+
838
+ if (node.concurrently) sql += ' CONCURRENTLY'
839
+ if (node.ifNotExists) sql += ' IF NOT EXISTS'
840
+
841
+ sql += ' ' + node.name
842
+ sql += ' ON ' + this.translate(node.table)
843
+
844
+ if (node.using) {
845
+ sql += ' USING ' + node.using.toUpperCase()
846
+ }
847
+
848
+ sql += ' (' + node.columns.map(c => {
849
+ let col = typeof c === 'string' ? c : c.column
850
+ if (c.direction) col += ' ' + c.direction.toUpperCase()
851
+ return col
852
+ }).join(', ') + ')'
853
+
854
+ if (node.where) {
855
+ sql += ' WHERE ' + this.generateCondition(node.where)
856
+ }
857
+
858
+ this.output += sql
859
+ return sql
860
+ }
861
+
862
+ generateCreateView(node) {
863
+ const savedOutput = this.output
864
+ this.output = ''
865
+
866
+ let sql = 'CREATE'
867
+
868
+ if (node.orReplace) sql += ' OR REPLACE'
869
+ if (node.temporary) sql += ' TEMPORARY'
870
+
871
+ sql += ' VIEW ' + node.name
872
+
873
+ if (node.columns && node.columns.length > 0) {
874
+ sql += ' (' + node.columns.join(', ') + ')'
875
+ }
876
+
877
+ sql += ' AS\n'
878
+ this.output = ''
879
+ this.generateSelect(node.select)
880
+ sql += this.output.trim()
881
+
882
+ this.output = savedOutput + sql
883
+ return sql
884
+ }
885
+
886
+ generateTransaction(node) {
887
+ const action = (node.action || node.type || '').toLowerCase()
888
+
889
+ switch (action) {
890
+ case 'begin':
891
+ case 'commencer':
892
+ this.output += 'BEGIN'
893
+ break
894
+ case 'commit':
895
+ case 'valider':
896
+ this.output += 'COMMIT'
897
+ break
898
+ case 'rollback':
899
+ case 'annuler':
900
+ this.output += 'ROLLBACK'
901
+ if (node.savepoint) {
902
+ this.output += ' TO SAVEPOINT ' + node.savepoint
903
+ }
904
+ break
905
+ case 'savepoint':
906
+ case 'point sauvegarde':
907
+ this.output += 'SAVEPOINT ' + node.name
908
+ break
909
+ }
910
+ }
911
+
912
+ generateGrant(node) {
913
+ let sql = 'GRANT '
914
+
915
+ sql += (node.privileges || ['ALL']).join(', ')
916
+ sql += ' ON ' + (node.objectType || 'TABLE') + ' ' + node.object
917
+ sql += ' TO ' + node.grantee
918
+
919
+ this.output += sql
920
+ return sql
921
+ }
922
+
923
+ generateRevoke(node) {
924
+ let sql = 'REVOKE '
925
+
926
+ sql += (node.privileges || ['ALL']).join(', ')
927
+ sql += ' ON ' + (node.objectType || 'TABLE') + ' ' + node.object
928
+ sql += ' FROM ' + node.grantee
929
+
930
+ this.output += sql
931
+ return sql
932
+ }
933
+
934
+ generateWith(node) {
935
+ const savedOutput = this.output
936
+ this.output = ''
937
+
938
+ let sql = 'WITH '
939
+
940
+ if (node.recursive) sql += 'RECURSIVE '
941
+
942
+ const ctes = (node.ctes || []).map(cte => {
943
+ let cteSql = cte.name
944
+ if (cte.columns) {
945
+ cteSql += ' (' + cte.columns.join(', ') + ')'
946
+ }
947
+ cteSql += ' AS (\n'
948
+ this.output = ''
949
+ this.generateSelect(cte.query)
950
+ cteSql += this.output.trim()
951
+ cteSql += '\n)'
952
+ return cteSql
953
+ })
954
+
955
+ sql += ctes.join(',\n')
956
+ sql += '\n'
957
+ this.output = ''
958
+ this.generateSelect(node.select)
959
+ sql += this.output.trim()
960
+
961
+ this.output = savedOutput + sql
962
+ return sql
963
+ }
964
+
965
+ generateUnion(node) {
966
+ const savedOutput = this.output
967
+ this.output = ''
968
+
969
+ const queries = node.queries || [node.left, node.right]
970
+ const type = node.all ? 'UNION ALL' : 'UNION'
971
+
972
+ const parts = []
973
+ for (const q of queries) {
974
+ this.output = ''
975
+ this.generateSelect(q)
976
+ parts.push(this.output.trim())
977
+ }
978
+
979
+ const sql = parts.join('\n' + type + '\n')
980
+
981
+ this.output = savedOutput + sql
982
+ return sql
983
+ }
984
+ }
985
+
986
+ module.exports = {
987
+ SQLGenerator
988
+ }