ts-prorm-orm 1.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 (310) hide show
  1. package/CHANGELOG.md +1111 -0
  2. package/LICENSE +21 -0
  3. package/README.md +573 -0
  4. package/dist/audit/history-query.js +180 -0
  5. package/dist/audit/index.js +23 -0
  6. package/dist/audit/logger.js +236 -0
  7. package/dist/cache/cache-manager.js +84 -0
  8. package/dist/cache/index.js +16 -0
  9. package/dist/cache/redis-cluster-cache.js +557 -0
  10. package/dist/cli.js +2200 -0
  11. package/dist/compliance/audit-trail.js +68 -0
  12. package/dist/compliance/backup-verification.js +685 -0
  13. package/dist/compliance/breach-detector.js +505 -0
  14. package/dist/compliance/consent-record.js +227 -0
  15. package/dist/compliance/consent-versioning.js +331 -0
  16. package/dist/compliance/cross-border-log.js +530 -0
  17. package/dist/compliance/data-classifier.js +258 -0
  18. package/dist/compliance/data-lineage.js +565 -0
  19. package/dist/compliance/data-masker.js +303 -0
  20. package/dist/compliance/data-portability.js +265 -0
  21. package/dist/compliance/data-retention.js +195 -0
  22. package/dist/compliance/dsar-workflow.js +342 -0
  23. package/dist/compliance/field-encryption.js +223 -0
  24. package/dist/compliance/immutable-record.js +148 -0
  25. package/dist/compliance/index.js +260 -0
  26. package/dist/compliance/privacy-impact-assessment.js +364 -0
  27. package/dist/compliance/pseudonymization.js +269 -0
  28. package/dist/compliance/query-firewall.js +1182 -0
  29. package/dist/compliance/rate-limiter.js +580 -0
  30. package/dist/compliance/right-to-erasure.js +117 -0
  31. package/dist/compliance/row-level-security.js +246 -0
  32. package/dist/compliance/security-decorator.js +574 -0
  33. package/dist/compliance/security-monitor.js +525 -0
  34. package/dist/compliance/sensitive-data-discovery.js +476 -0
  35. package/dist/compliance/session-isolation.js +472 -0
  36. package/dist/compliance/tls-enforcer.js +108 -0
  37. package/dist/compliance/worm-storage.js +712 -0
  38. package/dist/connection-manager.js +198 -0
  39. package/dist/connection-pool.js +519 -0
  40. package/dist/decorators/audit.js +136 -0
  41. package/dist/decorators/belongs-to-many.js +115 -0
  42. package/dist/decorators/belongs-to.js +115 -0
  43. package/dist/decorators/check.js +435 -0
  44. package/dist/decorators/collate.js +329 -0
  45. package/dist/decorators/comment.js +205 -0
  46. package/dist/decorators/database-settings.js +236 -0
  47. package/dist/decorators/default.js +244 -0
  48. package/dist/decorators/encryption.js +235 -0
  49. package/dist/decorators/engine.js +97 -0
  50. package/dist/decorators/fk-constraints.js +594 -0
  51. package/dist/decorators/foreign-table.js +136 -0
  52. package/dist/decorators/generated.js +274 -0
  53. package/dist/decorators/has-many.js +127 -0
  54. package/dist/decorators/has-one.js +116 -0
  55. package/dist/decorators/hstore.js +129 -0
  56. package/dist/decorators/index.js +355 -0
  57. package/dist/decorators/json-column.js +83 -0
  58. package/dist/decorators/jsonb.js +101 -0
  59. package/dist/decorators/orm-decorators.js +425 -0
  60. package/dist/decorators/permissions.js +294 -0
  61. package/dist/decorators/procedure.js +210 -0
  62. package/dist/decorators/query-options.js +556 -0
  63. package/dist/decorators/range.js +167 -0
  64. package/dist/decorators/set-column.js +83 -0
  65. package/dist/decorators/spatial.js +114 -0
  66. package/dist/decorators/storage.js +580 -0
  67. package/dist/decorators/timezone.js +512 -0
  68. package/dist/decorators/trigger.js +90 -0
  69. package/dist/decorators/uuid.js +135 -0
  70. package/dist/decorators/view.js +258 -0
  71. package/dist/diagrams/chen-diagram.js +354 -0
  72. package/dist/diagrams/class-diagram.js +384 -0
  73. package/dist/diagrams/dependency-diagram.js +432 -0
  74. package/dist/diagrams/er-diagram.js +605 -0
  75. package/dist/diagrams/flow-diagram.js +394 -0
  76. package/dist/diagrams/gantt-diagram.js +411 -0
  77. package/dist/diagrams/index-diagram.js +353 -0
  78. package/dist/diagrams/index.js +184 -0
  79. package/dist/diagrams/migration-diagram.js +316 -0
  80. package/dist/diagrams/model-diagram.js +616 -0
  81. package/dist/diagrams/package-diagram.js +376 -0
  82. package/dist/diagrams/palette.js +85 -0
  83. package/dist/diagrams/relational-diagram.js +455 -0
  84. package/dist/diagrams/schemadoc-diagram.js +309 -0
  85. package/dist/diagrams/sequence-diagram.js +307 -0
  86. package/dist/diagrams/state-diagram.js +344 -0
  87. package/dist/diagrams/svg-dom.js +111 -0
  88. package/dist/diagrams/tree-diagram.js +250 -0
  89. package/dist/dialects/clickhouse/index.js +1742 -0
  90. package/dist/dialects/cockroachdb/index.js +4677 -0
  91. package/dist/dialects/cratedb/index.js +441 -0
  92. package/dist/dialects/databricks/index.js +517 -0
  93. package/dist/dialects/db2/index.js +2225 -0
  94. package/dist/dialects/dialect.js +720 -0
  95. package/dist/dialects/duckdb/index.js +2014 -0
  96. package/dist/dialects/exasol/index.js +357 -0
  97. package/dist/dialects/firebird/index.js +495 -0
  98. package/dist/dialects/greenplum/index.js +457 -0
  99. package/dist/dialects/hana/index.js +1741 -0
  100. package/dist/dialects/mariadb/index.js +3590 -0
  101. package/dist/dialects/mssql/index.js +2296 -0
  102. package/dist/dialects/mysql/index.js +4058 -0
  103. package/dist/dialects/oracle/index.js +2999 -0
  104. package/dist/dialects/postgres/index.js +5319 -0
  105. package/dist/dialects/query-stream-helper.js +152 -0
  106. package/dist/dialects/questdb/index.js +433 -0
  107. package/dist/dialects/redshift/index.js +2277 -0
  108. package/dist/dialects/singlestore/index.js +354 -0
  109. package/dist/dialects/snowflake/index.js +2082 -0
  110. package/dist/dialects/spanner/index.js +1768 -0
  111. package/dist/dialects/sqlite/index.js +3382 -0
  112. package/dist/dialects/tidb/index.js +377 -0
  113. package/dist/dialects/timescaledb/index.js +164 -0
  114. package/dist/dialects/trino/index.js +420 -0
  115. package/dist/dialects/turso/index.js +227 -0
  116. package/dist/dialects/vertica/index.js +317 -0
  117. package/dist/dialects/yugabytedb/index.js +424 -0
  118. package/dist/errors/index.js +472 -0
  119. package/dist/errors/utils.js +340 -0
  120. package/dist/errors.js +21 -0
  121. package/dist/extensions/catalog/cloud-warehouse-features.js +207 -0
  122. package/dist/extensions/catalog/mssql-features.js +147 -0
  123. package/dist/extensions/catalog/mysql-mariadb-plugins.js +229 -0
  124. package/dist/extensions/catalog/oracle-db2-features.js +196 -0
  125. package/dist/extensions/catalog/postgres-extensions.js +516 -0
  126. package/dist/extensions/index.js +71 -0
  127. package/dist/extensions/types.js +13 -0
  128. package/dist/foreign-data.js +173 -0
  129. package/dist/hooks/hooks-manager.js +350 -0
  130. package/dist/hooks/index.js +37 -0
  131. package/dist/index.js +463 -0
  132. package/dist/logging.js +335 -0
  133. package/dist/migrations/index.js +40 -0
  134. package/dist/migrations/migration.js +196 -0
  135. package/dist/migrations/migrator.js +411 -0
  136. package/dist/migrations/prormmigration.js +275 -0
  137. package/dist/migrations/query-interface.js +435 -0
  138. package/dist/migrations/seeder.js +353 -0
  139. package/dist/models/associations.js +852 -0
  140. package/dist/models/constraints.js +288 -0
  141. package/dist/models/data-types.js +2518 -0
  142. package/dist/models/decorators.js +445 -0
  143. package/dist/models/index.js +33 -0
  144. package/dist/models/indexes.js +531 -0
  145. package/dist/models/methods.js +382 -0
  146. package/dist/models/model-manager.js +103 -0
  147. package/dist/models/model.js +5349 -0
  148. package/dist/models/operators.js +67 -0
  149. package/dist/models/scopes.js +189 -0
  150. package/dist/models/typescript-types.js +26 -0
  151. package/dist/nosql/aerospike/index.js +205 -0
  152. package/dist/nosql/allegrograph/index.js +169 -0
  153. package/dist/nosql/arangodb/index.js +364 -0
  154. package/dist/nosql/azure-blob/index.js +206 -0
  155. package/dist/nosql/beanstalkd/index.js +231 -0
  156. package/dist/nosql/beequeue/index.js +210 -0
  157. package/dist/nosql/bigchaindb/index.js +195 -0
  158. package/dist/nosql/bigtable/index.js +224 -0
  159. package/dist/nosql/blazegraph/index.js +173 -0
  160. package/dist/nosql/bullmq/index.js +191 -0
  161. package/dist/nosql/cassandra/index.js +174 -0
  162. package/dist/nosql/chroma/index.js +190 -0
  163. package/dist/nosql/cloudflare-kv/index.js +220 -0
  164. package/dist/nosql/coherence/index.js +200 -0
  165. package/dist/nosql/cosmosdb/index.js +157 -0
  166. package/dist/nosql/couchbase/index.js +213 -0
  167. package/dist/nosql/dax/index.js +212 -0
  168. package/dist/nosql/deno-kv/index.js +206 -0
  169. package/dist/nosql/dgraph/index.js +171 -0
  170. package/dist/nosql/doris/index.js +169 -0
  171. package/dist/nosql/druid/index.js +162 -0
  172. package/dist/nosql/dynamodb/index.js +937 -0
  173. package/dist/nosql/elasticsearch/index.js +377 -0
  174. package/dist/nosql/etcd/index.js +502 -0
  175. package/dist/nosql/eventhubs/index.js +213 -0
  176. package/dist/nosql/eventstore/index.js +254 -0
  177. package/dist/nosql/faunadb/index.js +188 -0
  178. package/dist/nosql/firestore/index.js +177 -0
  179. package/dist/nosql/fluree/index.js +148 -0
  180. package/dist/nosql/fuseki/index.js +170 -0
  181. package/dist/nosql/gcs/index.js +172 -0
  182. package/dist/nosql/gearman/index.js +160 -0
  183. package/dist/nosql/geode/index.js +196 -0
  184. package/dist/nosql/graphdb/index.js +169 -0
  185. package/dist/nosql/graylog/index.js +188 -0
  186. package/dist/nosql/gridgain/index.js +171 -0
  187. package/dist/nosql/hazelcast/index.js +162 -0
  188. package/dist/nosql/hbase/index.js +230 -0
  189. package/dist/nosql/ignite/index.js +173 -0
  190. package/dist/nosql/immudb/index.js +184 -0
  191. package/dist/nosql/index.js +232 -0
  192. package/dist/nosql/infinispan/index.js +200 -0
  193. package/dist/nosql/influxdb/index.js +0 -0
  194. package/dist/nosql/kafka/index.js +234 -0
  195. package/dist/nosql/keyspaces/index.js +189 -0
  196. package/dist/nosql/kinesis/index.js +253 -0
  197. package/dist/nosql/leveldb/index.js +153 -0
  198. package/dist/nosql/lmdb/index.js +160 -0
  199. package/dist/nosql/loki/index.js +201 -0
  200. package/dist/nosql/marklogic/index.js +204 -0
  201. package/dist/nosql/materialize/index.js +145 -0
  202. package/dist/nosql/meilisearch/index.js +154 -0
  203. package/dist/nosql/memcached/index.js +223 -0
  204. package/dist/nosql/milvus/index.js +410 -0
  205. package/dist/nosql/minio/index.js +265 -0
  206. package/dist/nosql/momento/index.js +179 -0
  207. package/dist/nosql/mongodb/index.js +461 -0
  208. package/dist/nosql/nats/index.js +247 -0
  209. package/dist/nosql/nedb/index.js +164 -0
  210. package/dist/nosql/neo4j/index.js +450 -0
  211. package/dist/nosql/neptune/index.js +470 -0
  212. package/dist/nosql/nsq/index.js +200 -0
  213. package/dist/nosql/opensearch/index.js +186 -0
  214. package/dist/nosql/orientdb/index.js +175 -0
  215. package/dist/nosql/papertrail/index.js +200 -0
  216. package/dist/nosql/pinecone/index.js +0 -0
  217. package/dist/nosql/pinot/index.js +133 -0
  218. package/dist/nosql/pouchdb/index.js +172 -0
  219. package/dist/nosql/prometheus/index.js +174 -0
  220. package/dist/nosql/provendb/index.js +147 -0
  221. package/dist/nosql/pubsub/index.js +187 -0
  222. package/dist/nosql/pulsar/index.js +232 -0
  223. package/dist/nosql/qdrant/index.js +295 -0
  224. package/dist/nosql/qldb/index.js +192 -0
  225. package/dist/nosql/r2/index.js +281 -0
  226. package/dist/nosql/rabbitmq/index.js +237 -0
  227. package/dist/nosql/ravendb/index.js +175 -0
  228. package/dist/nosql/redis/index.js +607 -0
  229. package/dist/nosql/redpanda/index.js +237 -0
  230. package/dist/nosql/resque/index.js +203 -0
  231. package/dist/nosql/rethinkdb/index.js +232 -0
  232. package/dist/nosql/rocksdb/index.js +152 -0
  233. package/dist/nosql/rockset/index.js +126 -0
  234. package/dist/nosql/s3/index.js +298 -0
  235. package/dist/nosql/scylladb/index.js +178 -0
  236. package/dist/nosql/signoz/index.js +227 -0
  237. package/dist/nosql/sns/index.js +201 -0
  238. package/dist/nosql/solr/index.js +231 -0
  239. package/dist/nosql/splunk/index.js +227 -0
  240. package/dist/nosql/sqs/index.js +246 -0
  241. package/dist/nosql/stardog/index.js +169 -0
  242. package/dist/nosql/starrocks/index.js +170 -0
  243. package/dist/nosql/store.js +2 -0
  244. package/dist/nosql/sumologic/index.js +213 -0
  245. package/dist/nosql/surrealdb/index.js +179 -0
  246. package/dist/nosql/terminusdb/index.js +151 -0
  247. package/dist/nosql/tigergraph/index.js +469 -0
  248. package/dist/nosql/typesense/index.js +148 -0
  249. package/dist/nosql/unqlite/index.js +157 -0
  250. package/dist/nosql/upstash/index.js +167 -0
  251. package/dist/nosql/vercel-kv/index.js +166 -0
  252. package/dist/nosql/victoriametrics/index.js +213 -0
  253. package/dist/nosql/virtuoso/index.js +169 -0
  254. package/dist/nosql/weaviate/index.js +276 -0
  255. package/dist/operators/index.js +108 -0
  256. package/dist/operators.js +2690 -0
  257. package/dist/prisma-migrate/index.js +38 -0
  258. package/dist/prisma-migrate/migration-generator.js +125 -0
  259. package/dist/prisma-migrate/model-generator.js +172 -0
  260. package/dist/prisma-migrate/relations.js +100 -0
  261. package/dist/prisma-migrate/schema-parser.js +167 -0
  262. package/dist/prisma-migrate/type-mapper.js +40 -0
  263. package/dist/prorm.js +6832 -0
  264. package/dist/query-builders/cte-builder.js +80 -0
  265. package/dist/query-builders/functions/aggregate.js +390 -0
  266. package/dist/query-builders/functions/conditional.js +503 -0
  267. package/dist/query-builders/functions/datetime.js +695 -0
  268. package/dist/query-builders/functions/fulltext.js +439 -0
  269. package/dist/query-builders/functions/index.js +93 -0
  270. package/dist/query-builders/functions/json.js +427 -0
  271. package/dist/query-builders/functions/math.js +399 -0
  272. package/dist/query-builders/functions/string.js +518 -0
  273. package/dist/query-builders/functions/window.js +328 -0
  274. package/dist/query-builders/include-builder.js +323 -0
  275. package/dist/query-builders/index-expression-builder.js +242 -0
  276. package/dist/query-builders/index.js +161 -0
  277. package/dist/query-builders/insert-builder.js +164 -0
  278. package/dist/query-builders/model-helpers.js +38 -0
  279. package/dist/query-builders/order-limit-builder.js +239 -0
  280. package/dist/query-builders/sql-compiler.js +2040 -0
  281. package/dist/query-builders/subquery-builder.js +152 -0
  282. package/dist/query-builders/update-builder.js +182 -0
  283. package/dist/query-builders/view-builder.js +218 -0
  284. package/dist/query-builders/where-builder.js +1532 -0
  285. package/dist/query-interface.js +622 -0
  286. package/dist/query-optimizers/batch-optimizer.js +426 -0
  287. package/dist/query-optimizers/explain-plans.js +415 -0
  288. package/dist/query-optimizers/index.js +51 -0
  289. package/dist/query-optimizers/prepared-statement-cache.js +423 -0
  290. package/dist/query-optimizers/query-hints.js +438 -0
  291. package/dist/query-optimizers/query-optimizer.js +278 -0
  292. package/dist/query-optimizers/slow-query-logger.js +271 -0
  293. package/dist/replica-manager.js +507 -0
  294. package/dist/schema/index.js +15 -0
  295. package/dist/schema/migration-generator.js +374 -0
  296. package/dist/schema/schema-differ.js +549 -0
  297. package/dist/schema/types.js +6 -0
  298. package/dist/sql-constants.js +300 -0
  299. package/dist/sqlite-advanced.js +1047 -0
  300. package/dist/streams/index.js +12 -0
  301. package/dist/streams/transforms.js +178 -0
  302. package/dist/transaction.js +456 -0
  303. package/dist/types/index.js +180 -0
  304. package/dist/user-management.js +127 -0
  305. package/dist/utils/date.js +300 -0
  306. package/dist/utils/index.js +1079 -0
  307. package/dist/utils/string.js +161 -0
  308. package/dist/validators/index.js +19 -0
  309. package/dist/validators/validator.js +911 -0
  310. package/package.json +190 -0
@@ -0,0 +1,1741 @@
1
+ "use strict";
2
+ /**
3
+ * SAP HANA dialect implementation for the TypeScript ORM.
4
+ * Uses the `hdb` driver - SAP's pure-JavaScript, callback-based HANA client
5
+ * (no native bindings, no bundled TypeScript types - see `src/dialects/hana/hdb.d.ts`
6
+ * for the minimal ambient shim written for it).
7
+ *
8
+ * Notable SAP HANA SQL characteristics implemented here:
9
+ * - Double-quoted, case-sensitive delimited identifiers (unquoted
10
+ * identifiers are folded to uppercase by HANA, like Oracle/Db2).
11
+ * - `LIMIT n OFFSET m` for pagination (standard, unlike Oracle/Db2's
12
+ * `OFFSET ... FETCH FIRST ...`) - HANA additionally requires a `LIMIT`
13
+ * whenever `OFFSET` is used, so a sentinel max `LIMIT` is emitted when
14
+ * only an offset is supplied.
15
+ * - `CREATE COLUMN TABLE` / `CREATE ROW TABLE` - HANA's column-store
16
+ * (default, controlled here via `TableOptions.columnStore`, default
17
+ * `true`) vs row-store table creation.
18
+ * - `GENERATED ALWAYS AS IDENTITY` for auto-increment columns.
19
+ * - `CREATE SEQUENCE` / `<sequence>.NEXTVAL` (via the single-row `DUMMY`
20
+ * pseudo-table, HANA's equivalent of Oracle's `DUAL`) for sequences.
21
+ * - Native `UPSERT <table> (...) VALUES (...) WITH PRIMARY KEY` statement
22
+ * (a genuine HANA-only construct, distinct from the `MERGE INTO` every
23
+ * other enterprise dialect here uses for upserts) plus full `MERGE INTO
24
+ * ... WHEN MATCHED / WHEN NOT MATCHED` support for more complex cases.
25
+ * - Parenthesized, multi-column `ALTER TABLE ... ADD (...)` / `DROP (...)`
26
+ * / `ALTER (...)` column DDL, and a dedicated `RENAME COLUMN t.old TO
27
+ * new` statement - all distinct from the single-column-at-a-time ALTER
28
+ * syntax used by most other dialects.
29
+ * - Mature `WITH` CTE (including recursive) and window/OLAP function
30
+ * support.
31
+ * - HANA-specific types: `NVARCHAR`, `SHORTTEXT`, `ALPHANUM`, `SECONDDATE`,
32
+ * `TINYINT`, `ST_GEOMETRY`/`ST_POINT` (spatial).
33
+ *
34
+ * Explicitly out of scope (not meaningful for a CRUD-oriented relational
35
+ * ORM layer):
36
+ * - `CREATE CALCULATION SCENARIO` / graphical Calculation Views - these are
37
+ * modeled objects normally authored in HANA's web-based modeler /
38
+ * Business Application Studio, not plain SQL DDL a CRUD ORM would emit.
39
+ * - Legacy `CE_` (Calculation Engine) functions - deprecated by SAP since
40
+ * HANA SPS09 in favor of plain SQL, and never had a "create" step an ORM
41
+ * would need to manage.
42
+ * - Full Smart Data Access federation (`CREATE REMOTE SOURCE` / `CREATE
43
+ * VIRTUAL TABLE`) - HANA's real analogue of a Postgres Foreign Data
44
+ * Wrapper, but out of scope for the generic (Postgres-shaped) FDW
45
+ * interface threaded through the shared `Dialect` type; the FDW methods
46
+ * below throw with a pointer to the real HANA feature instead of forcing
47
+ * a lossy 1:1 mapping.
48
+ */
49
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
50
+ if (k2 === undefined) k2 = k;
51
+ var desc = Object.getOwnPropertyDescriptor(m, k);
52
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
53
+ desc = { enumerable: true, get: function() { return m[k]; } };
54
+ }
55
+ Object.defineProperty(o, k2, desc);
56
+ }) : (function(o, m, k, k2) {
57
+ if (k2 === undefined) k2 = k;
58
+ o[k2] = m[k];
59
+ }));
60
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
61
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
62
+ }) : function(o, v) {
63
+ o["default"] = v;
64
+ });
65
+ var __importStar = (this && this.__importStar) || (function () {
66
+ var ownKeys = function(o) {
67
+ ownKeys = Object.getOwnPropertyNames || function (o) {
68
+ var ar = [];
69
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
70
+ return ar;
71
+ };
72
+ return ownKeys(o);
73
+ };
74
+ return function (mod) {
75
+ if (mod && mod.__esModule) return mod;
76
+ var result = {};
77
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
78
+ __setModuleDefault(result, mod);
79
+ return result;
80
+ };
81
+ })();
82
+ Object.defineProperty(exports, "__esModule", { value: true });
83
+ exports.HanaDialect = void 0;
84
+ const hdb = __importStar(require("hdb"));
85
+ const query_stream_helper_1 = require("../query-stream-helper");
86
+ const prorm_1 = require("../../prorm");
87
+ /** HANA requires a LIMIT whenever OFFSET is used; this sentinel stands in for "no limit". */
88
+ const HANA_MAX_LIMIT = 2147483647;
89
+ /**
90
+ * HANA-specific transaction class. Since `hdb` has no built-in connection
91
+ * pool, a transaction is backed by its own dedicated client connection
92
+ * (autocommit disabled) so its statements never interleave with unrelated
93
+ * queries running against the dialect's shared client.
94
+ */
95
+ class HanaTransaction {
96
+ constructor(client, options) {
97
+ this.finished = false;
98
+ this.parent = null;
99
+ this.savepoints = [];
100
+ this.client = null;
101
+ this.id = `tx_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
102
+ this.options = options || {};
103
+ this.client = client;
104
+ }
105
+ /**
106
+ * Commit the transaction's own dedicated connection directly. This mirrors
107
+ * `HanaDialect.commitTransaction()` (the path used when committing via the
108
+ * dialect) so that calling `tx.commit()` directly also actually commits and
109
+ * closes the connection, rather than merely flipping `finished` and
110
+ * leaking an open, uncommitted connection.
111
+ */
112
+ async commit() {
113
+ if (this.finished)
114
+ return;
115
+ if (this.client) {
116
+ await new Promise((resolve, reject) => {
117
+ this.client.commit((err) => (err ? reject(err) : resolve()));
118
+ });
119
+ this.client.close();
120
+ }
121
+ this.finished = true;
122
+ }
123
+ /** See `commit()` - mirrors `HanaDialect.rollbackTransaction()`. */
124
+ async rollback() {
125
+ if (this.finished)
126
+ return;
127
+ if (this.client) {
128
+ await new Promise((resolve, reject) => {
129
+ this.client.rollback((err) => (err ? reject(err) : resolve()));
130
+ });
131
+ this.client.close();
132
+ }
133
+ this.finished = true;
134
+ }
135
+ }
136
+ /**
137
+ * SAP HANA dialect class implementing the shared Dialect interface.
138
+ */
139
+ class HanaDialect {
140
+ constructor(config) {
141
+ this.name = 'hana';
142
+ this.library = 'hdb';
143
+ this.client = null;
144
+ this._isConnected = false;
145
+ this.config = {
146
+ host: 'localhost',
147
+ port: 30015,
148
+ database: undefined,
149
+ username: 'SYSTEM',
150
+ password: '',
151
+ ...config,
152
+ };
153
+ }
154
+ createClient() {
155
+ const options = {
156
+ host: this.config.host,
157
+ port: this.config.port,
158
+ user: this.config.username,
159
+ password: this.config.password,
160
+ ...(this.config.database ? { databaseName: this.config.database } : {}),
161
+ ...(this.config.useTLS ? { useTLS: true } : {}),
162
+ ...(this.config.extraOptions || {}),
163
+ };
164
+ return hdb.createClient(options);
165
+ }
166
+ connectClient(client) {
167
+ return new Promise((resolve, reject) => {
168
+ client.connect((err) => (err ? reject(err) : resolve()));
169
+ });
170
+ }
171
+ async connect() {
172
+ const client = this.createClient();
173
+ await this.connectClient(client);
174
+ this.client = client;
175
+ this._isConnected = true;
176
+ }
177
+ async disconnect() {
178
+ if (this.client) {
179
+ await new Promise((resolve, reject) => {
180
+ this.client.disconnect((err) => (err ? reject(err) : resolve()));
181
+ });
182
+ this.client = null;
183
+ this._isConnected = false;
184
+ }
185
+ }
186
+ getConnection() {
187
+ return this.client;
188
+ }
189
+ isConnected() {
190
+ return this._isConnected && this.client !== null && this.client.readyState === 'connected';
191
+ }
192
+ /**
193
+ * Execute `sql` against a specific hdb client, normalizing hdb's
194
+ * three-shaped callback result (nothing for DDL, an affected-row count
195
+ * for DML, an array of rows for a query) into the shared `QueryResult`
196
+ * shape.
197
+ *
198
+ * `CALL` statements are a distinct fourth shape (see README "Calling
199
+ * Stored Procedures"): hdb always invokes the callback as
200
+ * `cb(err, parameters, ...resultSets)` - a scalar OUT/INOUT parameters
201
+ * object (`{}` if none are declared) followed by zero or more result-set
202
+ * row arrays. Since `parameters` is a plain object rather than an array,
203
+ * it must be handled separately from the DML/query branches above or it
204
+ * silently falls into the `{ rowCount: 0, rows: [] }` default and both the
205
+ * output parameters and any result sets are lost.
206
+ */
207
+ executeOnClient(client, sql, params) {
208
+ const isCall = /^\s*CALL\b/i.test(sql.trim());
209
+ return new Promise((resolve, reject) => {
210
+ const handle = (err, ...rest) => {
211
+ if (err)
212
+ return reject(err);
213
+ if (isCall) {
214
+ const [outputParams, ...resultSets] = rest;
215
+ const [primaryResultSet, ...additionalResultSets] = resultSets;
216
+ const primaryRows = Array.isArray(primaryResultSet) ? primaryResultSet : [];
217
+ return resolve({
218
+ rows: primaryRows,
219
+ rowCount: primaryRows.length,
220
+ fields: [],
221
+ outputParams: outputParams && typeof outputParams === 'object' ? outputParams : {},
222
+ ...(additionalResultSets.length > 0 ? { resultSets: additionalResultSets } : {}),
223
+ });
224
+ }
225
+ const [result] = rest;
226
+ if (Array.isArray(result)) {
227
+ return resolve({ rows: result, rowCount: result.length, fields: [] });
228
+ }
229
+ // DML statements resolve with an affected-row count (or `undefined`
230
+ // for DDL); a plain SELECT that happens to hit an empty result set
231
+ // still resolves with an array from hdb.
232
+ return resolve({
233
+ rows: [],
234
+ rowCount: typeof result === 'number' ? result : 0,
235
+ fields: [],
236
+ });
237
+ };
238
+ if (params.length > 0) {
239
+ client.prepare(sql, (err, statement) => {
240
+ if (err)
241
+ return reject(err);
242
+ statement.exec(params, handle);
243
+ });
244
+ }
245
+ else {
246
+ client.exec(sql, handle);
247
+ }
248
+ });
249
+ }
250
+ /**
251
+ * Stream query results by paging through `sql` via repeated
252
+ * dialect-appropriate LIMIT/OFFSET queries (see
253
+ * `createPaginatedQueryStream()` in `src/dialects/query-stream-helper.ts`)
254
+ * instead of loading the whole result set into memory at once.
255
+ * @param sql - The SELECT statement to stream
256
+ * @param options - Streaming options (batch size, backpressure watermark, model mapping)
257
+ */
258
+ queryStream(sql, options) {
259
+ return (0, query_stream_helper_1.createPaginatedQueryStream)(this, sql, options);
260
+ }
261
+ async query(sql, options) {
262
+ const bindValues = options?.replacements ?? options?.bindings ?? options?.bind ?? [];
263
+ const params = Array.isArray(bindValues) ? bindValues : Object.values(bindValues);
264
+ const tx = options?.transaction;
265
+ if (tx) {
266
+ if (tx.finished) {
267
+ throw new Error('Cannot execute query: the transaction has already been committed or rolled back.');
268
+ }
269
+ if (!tx.client) {
270
+ throw new Error('Cannot execute query: the transaction has no associated connection.');
271
+ }
272
+ return this.executeOnClient(tx.client, sql, params);
273
+ }
274
+ if (!this.client) {
275
+ throw new Error('Not connected to database. Call connect() first.');
276
+ }
277
+ return this.executeOnClient(this.client, sql, params);
278
+ }
279
+ escape(value) {
280
+ if (value instanceof prorm_1.Literal) {
281
+ return value.val;
282
+ }
283
+ if (value === null || value === undefined)
284
+ return 'NULL';
285
+ if (typeof value === 'boolean')
286
+ return value ? 'TRUE' : 'FALSE';
287
+ if (typeof value === 'number')
288
+ return String(value);
289
+ if (value instanceof Date)
290
+ return this.formatTimestamp(value);
291
+ if (Buffer.isBuffer(value))
292
+ return `X'${value.toString('hex')}'`;
293
+ if (Array.isArray(value) || typeof value === 'object') {
294
+ return `'${JSON.stringify(value).replace(/'/g, "''")}'`;
295
+ }
296
+ return `'${String(value).replace(/'/g, "''")}'`;
297
+ }
298
+ formatTimestamp(date) {
299
+ const pad = (n, len = 2) => String(n).padStart(len, '0');
300
+ const year = date.getFullYear();
301
+ const month = pad(date.getMonth() + 1);
302
+ const day = pad(date.getDate());
303
+ const hours = pad(date.getHours());
304
+ const minutes = pad(date.getMinutes());
305
+ const seconds = pad(date.getSeconds());
306
+ const millis = pad(date.getMilliseconds(), 3);
307
+ return `TIMESTAMP '${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${millis}'`;
308
+ }
309
+ /**
310
+ * HANA uses double-quoted, case-sensitive delimited identifiers.
311
+ * Unquoted identifiers are folded to uppercase by HANA, so identifiers
312
+ * created via quoteIdentifier()/escapeId() must always be quoted the same
313
+ * way on every reference.
314
+ */
315
+ escapeId(identifier) {
316
+ const id = String(identifier ?? '');
317
+ return `"${id.replace(/"/g, '""')}"`;
318
+ }
319
+ quoteIdentifier(identifier) {
320
+ return this.escapeId(identifier);
321
+ }
322
+ quoteTable(tableName, schema) {
323
+ const schemaName = schema || this.config.schema;
324
+ if (schemaName) {
325
+ return `${this.escapeId(schemaName)}.${this.escapeId(tableName)}`;
326
+ }
327
+ return this.escapeId(tableName);
328
+ }
329
+ /**
330
+ * Build a `SCHEMA_NAME` predicate for `SYS.*` catalog-view introspection
331
+ * queries. Without this, queries filtered only by e.g. `TABLE_NAME` match
332
+ * every same-named table across every schema on a multi-schema HANA
333
+ * system, silently merging/corrupting metadata. Scopes to the configured
334
+ * default schema when set, otherwise to the session's `CURRENT_SCHEMA`
335
+ * (mirrors the fallback `quoteTable()` uses for unqualified references).
336
+ */
337
+ schemaFilterSql(schema) {
338
+ const schemaName = schema || this.config.schema;
339
+ return schemaName
340
+ ? `AND SCHEMA_NAME = '${schemaName.toUpperCase()}'`
341
+ : 'AND SCHEMA_NAME = CURRENT_SCHEMA';
342
+ }
343
+ async getDatabaseVersion() {
344
+ const result = await this.query('SELECT VERSION FROM SYS.M_DATABASE');
345
+ return result.rows[0]?.VERSION || result.rows[0]?.version || 'unknown';
346
+ }
347
+ /**
348
+ * Parses `SYS.M_DATABASE.VERSION` to tell HANA Cloud apart from
349
+ * on-premise HANA. HANA Cloud is continuously delivered and reports a
350
+ * `4.x`-prefixed version (e.g. `4.00.000.00.1234567890`); on-premise HANA
351
+ * 2.0 SPS releases report `2.x` (e.g. `2.00.059.00.1234567890`, where the
352
+ * `059` third segment is the SPS/revision number) and legacy HANA 1.0
353
+ * reports `1.x`. This is purely additive metadata for callers that want
354
+ * to branch on edition-specific feature availability (e.g. Cloud-only
355
+ * features) - it doesn't change behavior of any existing method.
356
+ */
357
+ async getEditionInfo() {
358
+ const version = await this.getDatabaseVersion();
359
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)/);
360
+ if (!match) {
361
+ return { edition: 'unknown', version };
362
+ }
363
+ const major = Number(match[1]);
364
+ const minor = Number(match[2]);
365
+ const revision = Number(match[3]);
366
+ const edition = major >= 4 ? 'cloud' : major >= 1 ? 'on-premise' : 'unknown';
367
+ return { edition, version, major, minor, revision };
368
+ }
369
+ /** Convenience wrapper over `getEditionInfo()` for the common case of a boolean check. */
370
+ async isCloudEdition() {
371
+ const { edition } = await this.getEditionInfo();
372
+ return edition === 'cloud';
373
+ }
374
+ // ==================== Schema Operations ====================
375
+ async createSchema(schema) {
376
+ await this.query(`CREATE SCHEMA ${this.escapeId(schema)}`);
377
+ }
378
+ async dropSchema(schema, options) {
379
+ if (options?.ifExists) {
380
+ const exists = await this.query(`SELECT SCHEMA_NAME FROM SYS.SCHEMAS WHERE SCHEMA_NAME = '${schema.toUpperCase()}'`);
381
+ if (!exists.rows || exists.rows.length === 0)
382
+ return;
383
+ }
384
+ let sql = `DROP SCHEMA ${this.escapeId(schema)}`;
385
+ if (options?.cascade)
386
+ sql += ' CASCADE';
387
+ await this.query(sql);
388
+ }
389
+ async showAllSchemas() {
390
+ const result = await this.query('SELECT SCHEMA_NAME FROM SYS.SCHEMAS ORDER BY SCHEMA_NAME');
391
+ return result.rows.map((row) => row.SCHEMA_NAME || row.schema_name);
392
+ }
393
+ async listSchemas() {
394
+ return this.showAllSchemas();
395
+ }
396
+ createDatabaseSQL(options) {
397
+ // Tenant databases in a HANA MDC landscape are created via
398
+ // `CREATE DATABASE <name> SYSTEM USER PASSWORD "..."` issued against the
399
+ // SYSTEMDB, not a per-tenant SQL session - this returns the DDL shape for
400
+ // completeness, but most deployments provision tenants operationally
401
+ // rather than through application code.
402
+ return `CREATE DATABASE ${this.escapeId(options.name)} SYSTEM USER PASSWORD "Change_Me1"`;
403
+ }
404
+ dropDatabaseSQL(name) {
405
+ return `DROP DATABASE ${this.escapeId(name)}`;
406
+ }
407
+ createSavepointSQL(name) {
408
+ const savepointName = name || `sp_${Date.now()}`;
409
+ return `SAVEPOINT ${savepointName}`;
410
+ }
411
+ releaseSavepointSQL(name) {
412
+ return `RELEASE SAVEPOINT ${name}`;
413
+ }
414
+ rollbackToSavepointSQL(name) {
415
+ return `ROLLBACK TO SAVEPOINT ${name}`;
416
+ }
417
+ // ==================== Extensions (PostgreSQL-only, not supported) ====================
418
+ async createExtension() {
419
+ throw new Error('Extensions are a PostgreSQL-specific feature. HANA has no CREATE EXTENSION equivalent.');
420
+ }
421
+ async dropExtension() {
422
+ throw new Error('Extensions are a PostgreSQL-specific feature. HANA has no CREATE EXTENSION equivalent.');
423
+ }
424
+ async getExtensions() {
425
+ throw new Error('Extensions are a PostgreSQL-specific feature. HANA has no CREATE EXTENSION equivalent.');
426
+ }
427
+ async hasExtension() {
428
+ throw new Error('Extensions are a PostgreSQL-specific feature. HANA has no CREATE EXTENSION equivalent.');
429
+ }
430
+ // ==================== Table Operations ====================
431
+ buildColumnDefinition(columnName, definition) {
432
+ let sql = `${this.escapeId(columnName)} ${this.getDataTypeSql(definition.type)}`;
433
+ if (definition.autoIncrement) {
434
+ sql += ' GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1)';
435
+ }
436
+ if (definition.allowNull === false) {
437
+ sql += ' NOT NULL';
438
+ }
439
+ if (definition.defaultValue !== undefined && !definition.autoIncrement) {
440
+ if (definition.defaultValue === null) {
441
+ sql += ' DEFAULT NULL';
442
+ }
443
+ else if (definition.defaultValue instanceof prorm_1.Literal) {
444
+ sql += ` DEFAULT ${definition.defaultValue.val}`;
445
+ }
446
+ else {
447
+ sql += ` DEFAULT ${this.escape(definition.defaultValue)}`;
448
+ }
449
+ }
450
+ if (definition.primaryKey) {
451
+ sql += ' PRIMARY KEY';
452
+ }
453
+ if (definition.unique) {
454
+ sql += ' UNIQUE';
455
+ }
456
+ if (definition.references) {
457
+ const refField = definition.references.field;
458
+ const refFieldSql = Array.isArray(refField)
459
+ ? `(${refField.map((f) => this.escapeId(f)).join(', ')})`
460
+ : `(${this.escapeId(refField)})`;
461
+ sql += ` REFERENCES ${this.quoteTable(definition.references.table)}${refFieldSql}`;
462
+ if (definition.references.onDelete) {
463
+ sql += ` ON DELETE ${definition.references.onDelete.toUpperCase()}`;
464
+ }
465
+ if (definition.references.onUpdate) {
466
+ sql += ` ON UPDATE ${definition.references.onUpdate.toUpperCase()}`;
467
+ }
468
+ }
469
+ return sql;
470
+ }
471
+ buildConstraintSql(constraint) {
472
+ const name = constraint.name ? `CONSTRAINT ${this.escapeId(constraint.name)} ` : '';
473
+ const fields = constraint.fields?.map((f) => this.escapeId(f)).join(', ') || '';
474
+ switch (constraint.type) {
475
+ case 'PRIMARY KEY':
476
+ return `${name}PRIMARY KEY (${fields})`;
477
+ case 'UNIQUE':
478
+ return `${name}UNIQUE (${fields})`;
479
+ case 'CHECK':
480
+ return `${name}CHECK (${constraint.check})`;
481
+ case 'FOREIGN KEY': {
482
+ if (!constraint.references)
483
+ return null;
484
+ const refField = constraint.references.field;
485
+ const refFieldSql = Array.isArray(refField)
486
+ ? `(${refField.map((f) => this.escapeId(f)).join(', ')})`
487
+ : `(${this.escapeId(refField)})`;
488
+ let fkSql = `${name}FOREIGN KEY (${fields}) REFERENCES ${this.quoteTable(constraint.references.table)}${refFieldSql}`;
489
+ if (constraint.references.onDelete) {
490
+ fkSql += ` ON DELETE ${constraint.references.onDelete.toUpperCase()}`;
491
+ }
492
+ if (constraint.references.onUpdate) {
493
+ fkSql += ` ON UPDATE ${constraint.references.onUpdate.toUpperCase()}`;
494
+ }
495
+ return fkSql;
496
+ }
497
+ default:
498
+ return null;
499
+ }
500
+ }
501
+ /**
502
+ * Normalizes `TableOptions.systemVersioning` - which may be a bare
503
+ * `boolean` (the shared shape also used by MariaDB/SQL Server-style
504
+ * dialects) or a HANA-specific `{ historyTable?, validFromColumn?,
505
+ * validToColumn? }` config object - down to `undefined` (versioning off)
506
+ * or a config object (versioning on, using period-column defaults when a
507
+ * bare `true` was passed).
508
+ */
509
+ normalizeSystemVersioningOptions(sv) {
510
+ if (!sv)
511
+ return undefined;
512
+ return sv === true ? {} : sv;
513
+ }
514
+ /**
515
+ * Builds the three DDL fragments HANA needs for a system-versioned
516
+ * (temporal) table: the `GENERATED ALWAYS AS ROW START`/`ROW END` period
517
+ * columns and the `PERIOD FOR SYSTEM_TIME (...)` clause. Shared between
518
+ * `createTable` (inline, inside the column list) and
519
+ * `alterTableSystemVersioning` (issued as separate `ALTER TABLE`
520
+ * statements against an existing table).
521
+ *
522
+ * Only `TIMESTAMP` is a valid period-column type in HANA, and the
523
+ * precision is fixed at `TIMESTAMP(6)` (microseconds) to match SAP's own
524
+ * examples for `PERIOD FOR SYSTEM_TIME`.
525
+ */
526
+ buildSystemVersioningPeriodSql(sv) {
527
+ const validFrom = sv.validFromColumn || 'valid_from';
528
+ const validTo = sv.validToColumn || 'valid_to';
529
+ return {
530
+ validFrom,
531
+ validTo,
532
+ rowStartColumnSql: `${this.escapeId(validFrom)} TIMESTAMP(6) NOT NULL GENERATED ALWAYS AS ROW START`,
533
+ rowEndColumnSql: `${this.escapeId(validTo)} TIMESTAMP(6) NOT NULL GENERATED ALWAYS AS ROW END`,
534
+ periodClauseSql: `PERIOD FOR SYSTEM_TIME (${this.escapeId(validFrom)}, ${this.escapeId(validTo)})`,
535
+ };
536
+ }
537
+ /**
538
+ * Create a table. HANA tables are column-store by default; pass
539
+ * `{ columnStore: false }` (an extra, HANA-specific option threaded
540
+ * through the shared `TableOptions` as an untyped property, the same
541
+ * pattern other dialect files use for their own extensions) to create a
542
+ * legacy row-store table instead via `CREATE ROW TABLE`.
543
+ *
544
+ * `options.systemVersioning` opts the table into HANA's system-versioned
545
+ * (temporal history) support: the row-start/row-end period columns and
546
+ * `PERIOD FOR SYSTEM_TIME (...)` clause are appended to the column list,
547
+ * and the statement is closed with `WITH SYSTEM VERSIONING [HISTORY TABLE
548
+ * ...]`, e.g.:
549
+ * CREATE COLUMN TABLE "t" (..., "valid_from" TIMESTAMP(6) NOT NULL
550
+ * GENERATED ALWAYS AS ROW START, "valid_to" TIMESTAMP(6) NOT NULL
551
+ * GENERATED ALWAYS AS ROW END, PERIOD FOR SYSTEM_TIME ("valid_from",
552
+ * "valid_to")) WITH SYSTEM VERSIONING HISTORY TABLE "t_history"
553
+ * When `historyTable` is omitted the `HISTORY TABLE` clause is left off
554
+ * entirely and HANA auto-generates (and manages) the history table itself.
555
+ * System-versioned tables are only supported on column-store tables, so
556
+ * this throws if combined with `{ columnStore: false }`.
557
+ */
558
+ async createTable(tableName, columns, options) {
559
+ const columnDefs = [];
560
+ for (const [columnName, definition] of Object.entries(columns)) {
561
+ columnDefs.push(this.buildColumnDefinition(columnName, definition));
562
+ }
563
+ if (options?.constraints) {
564
+ for (const constraint of options.constraints) {
565
+ const constraintSql = this.buildConstraintSql(constraint);
566
+ if (constraintSql)
567
+ columnDefs.push(constraintSql);
568
+ }
569
+ }
570
+ const columnStore = options?.columnStore !== false;
571
+ const tableKind = columnStore ? 'COLUMN' : 'ROW';
572
+ const systemVersioning = this.normalizeSystemVersioningOptions(options?.systemVersioning);
573
+ if (systemVersioning && !columnStore) {
574
+ throw new Error('HANA system-versioned tables require a column-store table (columnStore: false was set)');
575
+ }
576
+ if (systemVersioning) {
577
+ const { rowStartColumnSql, rowEndColumnSql, periodClauseSql } = this.buildSystemVersioningPeriodSql(systemVersioning);
578
+ columnDefs.push(rowStartColumnSql, rowEndColumnSql, periodClauseSql);
579
+ }
580
+ let sql = `CREATE ${tableKind} TABLE ${this.quoteTable(tableName)} (${columnDefs.join(', ')})`;
581
+ if (systemVersioning) {
582
+ sql += ' WITH SYSTEM VERSIONING';
583
+ if (systemVersioning.historyTable) {
584
+ sql += ` HISTORY TABLE ${this.quoteTable(systemVersioning.historyTable)}`;
585
+ }
586
+ }
587
+ await this.query(sql);
588
+ if (options?.indexes) {
589
+ for (const index of options.indexes) {
590
+ await this.createIndex(tableName, {
591
+ name: index.name || `${tableName}_${index.fields.join('_')}_idx`,
592
+ fields: index.fields,
593
+ unique: index.unique,
594
+ type: index.type,
595
+ });
596
+ }
597
+ }
598
+ }
599
+ /**
600
+ * Enables system-versioning (temporal history) on an *already-existing*
601
+ * table. Unlike `CREATE TABLE ... WITH SYSTEM VERSIONING`, HANA has no
602
+ * single-statement form for retrofitting an existing table - the
603
+ * row-start/row-end period columns, the `PERIOD FOR SYSTEM_TIME` clause,
604
+ * and the `SYSTEM VERSIONING` clause itself must each be added via their
605
+ * own `ALTER TABLE` statement, in this order:
606
+ * ALTER TABLE "t" ADD ("valid_from" TIMESTAMP(6) NOT NULL GENERATED ALWAYS AS ROW START)
607
+ * ALTER TABLE "t" ADD ("valid_to" TIMESTAMP(6) NOT NULL GENERATED ALWAYS AS ROW END)
608
+ * ALTER TABLE "t" ADD PERIOD FOR SYSTEM_TIME ("valid_from", "valid_to")
609
+ * ALTER TABLE "t" ADD SYSTEM VERSIONING [HISTORY TABLE "t_history"]
610
+ * As with `createTable`, an omitted `historyTable` leaves the `HISTORY
611
+ * TABLE` clause off and lets HANA auto-generate/manage the history table.
612
+ */
613
+ async alterTableSystemVersioning(tableName, options) {
614
+ const sv = options || {};
615
+ const { rowStartColumnSql, rowEndColumnSql, periodClauseSql } = this.buildSystemVersioningPeriodSql(sv);
616
+ const table = this.quoteTable(tableName);
617
+ await this.query(`ALTER TABLE ${table} ADD (${rowStartColumnSql})`);
618
+ await this.query(`ALTER TABLE ${table} ADD (${rowEndColumnSql})`);
619
+ await this.query(`ALTER TABLE ${table} ADD ${periodClauseSql}`);
620
+ let sql = `ALTER TABLE ${table} ADD SYSTEM VERSIONING`;
621
+ if (sv.historyTable) {
622
+ sql += ` HISTORY TABLE ${this.quoteTable(sv.historyTable)}`;
623
+ }
624
+ await this.query(sql);
625
+ }
626
+ async dropTable(tableName, options) {
627
+ if (options?.ifExists) {
628
+ const exists = await this.query(`SELECT TABLE_NAME FROM SYS.TABLES WHERE TABLE_NAME = '${tableName.toUpperCase()}' ${this.schemaFilterSql()}`);
629
+ if (!exists.rows || exists.rows.length === 0)
630
+ return;
631
+ }
632
+ let sql = `DROP TABLE ${this.quoteTable(tableName)}`;
633
+ if (options?.cascade)
634
+ sql += ' CASCADE';
635
+ await this.query(sql);
636
+ }
637
+ /**
638
+ * HANA range/hash partitioning is expressed via a `PARTITION BY` clause on
639
+ * the `CREATE (COLUMN|ROW) TABLE` statement itself. HANA also supports
640
+ * `ROUNDROBIN` partitioning, which isn't modeled by the shared
641
+ * `partitionBy.type` union.
642
+ */
643
+ async createPartitionedTable(tableName, columns, options) {
644
+ if (!options?.partitionBy || (options.partitionBy.type !== 'range' && options.partitionBy.type !== 'hash')) {
645
+ throw new Error('HANA supports RANGE, HASH and ROUNDROBIN partitioning; only "range" and "hash" are modeled by createPartitionedTable (use raw SQL via query() for ROUNDROBIN).');
646
+ }
647
+ const columnDefs = Object.entries(columns).map(([columnName, definition]) => this.buildColumnDefinition(columnName, definition));
648
+ const columnStore = options?.columnStore !== false;
649
+ const tableKind = columnStore ? 'COLUMN' : 'ROW';
650
+ const partitionColumns = Array.isArray(options.partitionBy.column)
651
+ ? options.partitionBy.column.map((c) => this.escapeId(c)).join(', ')
652
+ : this.escapeId(options.partitionBy.column);
653
+ let partitionClause;
654
+ if (options.partitionBy.type === 'hash') {
655
+ const count = (options.partitions || []).length || 4;
656
+ partitionClause = `PARTITION BY HASH (${partitionColumns}) PARTITIONS ${count}`;
657
+ }
658
+ else {
659
+ const parts = (options.partitions || []).map((part) => {
660
+ if (part.bound?.to !== undefined) {
661
+ return `PARTITION ${this.formatPartitionValue(part.bound.to)}`;
662
+ }
663
+ return 'PARTITION OTHERS';
664
+ });
665
+ partitionClause = `PARTITION BY RANGE (${partitionColumns}) (${parts.join(', ')})`;
666
+ }
667
+ const sql = `CREATE ${tableKind} TABLE ${this.quoteTable(tableName)} (${columnDefs.join(', ')}) ${partitionClause}`;
668
+ await this.query(sql);
669
+ }
670
+ formatPartitionValue(value) {
671
+ if (value instanceof Date)
672
+ return `VALUE <= ${this.escape(value)}`;
673
+ if (typeof value === 'number')
674
+ return `VALUE <= ${value}`;
675
+ return `VALUE <= ${this.escape(value)}`;
676
+ }
677
+ async createPartition(options) {
678
+ const bound = options.bound && 'to' in options.bound ? this.formatPartitionValue(options.bound.to) : 'OTHERS';
679
+ await this.query(`ALTER TABLE ${this.quoteTable(options.parentTable)} ADD PARTITION ${bound}`);
680
+ }
681
+ async attachPartition(_options) {
682
+ throw new Error('HANA does not support attaching a standalone table as a partition; use ALTER TABLE ... ADD PARTITION instead.');
683
+ }
684
+ async detachPartition(_options) {
685
+ throw new Error('HANA does not support detaching a partition into a standalone table; use ALTER TABLE ... DROP PARTITION or MERGE PARTITION instead.');
686
+ }
687
+ async dropPartition(partitionName) {
688
+ await this.query(`ALTER TABLE ${this.quoteTable(partitionName)} MERGE PARTITIONS`);
689
+ }
690
+ /**
691
+ * Bulk insert records. HANA supports multi-row `VALUES (...), (...), ...`
692
+ * in a single `INSERT INTO` statement (as does most SQL-2003-compliant
693
+ * engines), so all rows are sent in one statement.
694
+ */
695
+ async bulkInsert(tableName, records, options) {
696
+ if (records.length === 0) {
697
+ return { rows: [], rowCount: 0, fields: [] };
698
+ }
699
+ const columns = Object.keys(records[0]);
700
+ const values = [];
701
+ const rowPlaceholders = [];
702
+ for (const record of records) {
703
+ const placeholders = [];
704
+ for (const column of columns) {
705
+ placeholders.push('?');
706
+ values.push(record[column]);
707
+ }
708
+ rowPlaceholders.push(`(${placeholders.join(', ')})`);
709
+ }
710
+ const sql = `INSERT INTO ${this.quoteTable(tableName, options?.schema)} (${columns.map((c) => this.escapeId(c)).join(', ')}) VALUES ${rowPlaceholders.join(', ')}`;
711
+ return this.query(sql, { replacements: values });
712
+ }
713
+ // ==================== View Operations ====================
714
+ async createView(viewName, query, options) {
715
+ if (options?.replace) {
716
+ await this.query(`DROP VIEW ${this.escapeId(viewName)}`).catch(() => undefined);
717
+ }
718
+ await this.query(`CREATE VIEW ${this.escapeId(viewName)} AS ${query}`);
719
+ }
720
+ async dropView(viewName, options) {
721
+ if (options?.ifExists) {
722
+ const exists = await this.query(`SELECT VIEW_NAME FROM SYS.VIEWS WHERE VIEW_NAME = '${viewName.toUpperCase()}' ${this.schemaFilterSql()}`);
723
+ if (!exists.rows || exists.rows.length === 0)
724
+ return;
725
+ }
726
+ await this.query(`DROP VIEW ${this.escapeId(viewName)}`);
727
+ }
728
+ async showViews() {
729
+ const result = await this.query('SELECT VIEW_NAME FROM SYS.VIEWS ORDER BY VIEW_NAME');
730
+ return result.rows.map((row) => row.VIEW_NAME || row.view_name);
731
+ }
732
+ // HANA has no `CREATE MATERIALIZED VIEW` SQL statement - the closest
733
+ // concepts are Calculation Views (modeled objects, out of scope for this
734
+ // ORM) or a manually-maintained "snapshot" table populated by a
735
+ // stored-procedure-driven refresh job. These throw rather than emit
736
+ // invalid SQL.
737
+ async createMaterializedView(_options) {
738
+ throw new Error('HANA has no CREATE MATERIALIZED VIEW statement. Use a Calculation View or a manually-refreshed snapshot table instead.');
739
+ }
740
+ async refreshMaterializedView(_viewName, _options) {
741
+ throw new Error('HANA has no materialized views to refresh.');
742
+ }
743
+ async dropMaterializedView(_viewName, _options) {
744
+ throw new Error('HANA has no materialized views to drop.');
745
+ }
746
+ async hasMaterializedView(_viewName) {
747
+ return false;
748
+ }
749
+ async showMaterializedViews() {
750
+ return [];
751
+ }
752
+ // ==================== Column Operations ====================
753
+ // HANA's ALTER TABLE column DDL is parenthesized and can batch multiple
754
+ // columns in one clause - distinct from the single-column-at-a-time ALTER
755
+ // COLUMN syntax used by most other dialects.
756
+ async addColumn(tableName, columnName, definition) {
757
+ await this.query(`ALTER TABLE ${this.quoteTable(tableName)} ADD (${this.buildColumnDefinition(columnName, definition)})`);
758
+ }
759
+ async removeColumn(tableName, columnName) {
760
+ await this.query(`ALTER TABLE ${this.quoteTable(tableName)} DROP (${this.escapeId(columnName)})`);
761
+ }
762
+ async changeColumn(tableName, columnName, definition) {
763
+ let clause = `${this.escapeId(columnName)} ${this.getDataTypeSql(definition.type)}`;
764
+ if (definition.defaultValue !== undefined) {
765
+ clause += ` DEFAULT ${definition.defaultValue === null ? 'NULL' : this.escape(definition.defaultValue)}`;
766
+ }
767
+ if (definition.allowNull === false) {
768
+ clause += ' NOT NULL';
769
+ }
770
+ await this.query(`ALTER TABLE ${this.quoteTable(tableName)} ALTER (${clause})`);
771
+ }
772
+ /** HANA's dedicated column-rename statement: `RENAME COLUMN t.old TO new`. */
773
+ async renameColumn(tableName, oldColumnName, newColumnName) {
774
+ await this.query(`RENAME COLUMN ${this.quoteTable(tableName)}.${this.escapeId(oldColumnName)} TO ${this.escapeId(newColumnName)}`);
775
+ }
776
+ async addForeignKey(tableName, columnName, referencedTableName, referencedColumnName, options) {
777
+ const constraintName = options?.name
778
+ ? this.escapeId(options.name)
779
+ : this.escapeId(`fk_${tableName}_${columnName}`);
780
+ let sql = `ALTER TABLE ${this.quoteTable(tableName)} ADD CONSTRAINT ${constraintName} FOREIGN KEY (${this.escapeId(columnName)}) REFERENCES ${this.quoteTable(referencedTableName)} (${this.escapeId(referencedColumnName)})`;
781
+ if (options?.onDelete)
782
+ sql += ` ON DELETE ${options.onDelete.toUpperCase()}`;
783
+ if (options?.onUpdate)
784
+ sql += ` ON UPDATE ${options.onUpdate.toUpperCase()}`;
785
+ await this.query(sql);
786
+ }
787
+ async describeTable(tableName) {
788
+ const result = await this.query(`SELECT COLUMN_NAME, DATA_TYPE_NAME, IS_NULLABLE, DEFAULT_VALUE, GENERATED_ALWAYS_AS_IDENTITY
789
+ FROM SYS.TABLE_COLUMNS
790
+ WHERE TABLE_NAME = '${tableName.toUpperCase()}' ${this.schemaFilterSql()}
791
+ ORDER BY POSITION`);
792
+ const pkResult = await this.query(`SELECT COLUMN_NAME FROM SYS.CONSTRAINTS WHERE TABLE_NAME = '${tableName.toUpperCase()}' AND IS_PRIMARY_KEY = 'TRUE' ${this.schemaFilterSql()}`);
793
+ const pkColumns = new Set(pkResult.rows.map((row) => (row.COLUMN_NAME || row.column_name || '').toUpperCase()));
794
+ const description = {};
795
+ for (const row of result.rows) {
796
+ const rawName = row.COLUMN_NAME || row.column_name || '';
797
+ const colName = rawName.toLowerCase();
798
+ description[colName] = {
799
+ type: row.DATA_TYPE_NAME || row.data_type_name,
800
+ allowNull: (row.IS_NULLABLE ?? row.is_nullable) === 'TRUE',
801
+ defaultValue: row.DEFAULT_VALUE ?? row.default_value ?? null,
802
+ primaryKey: pkColumns.has(rawName.toUpperCase()),
803
+ autoIncrement: (row.GENERATED_ALWAYS_AS_IDENTITY ?? row.generated_always_as_identity) === 'TRUE',
804
+ };
805
+ }
806
+ return description;
807
+ }
808
+ async renameTable(oldName, newName) {
809
+ await this.query(`RENAME TABLE ${this.quoteTable(oldName)} TO ${this.escapeId(newName)}`);
810
+ }
811
+ async showTables() {
812
+ const result = await this.query(`SELECT TABLE_NAME FROM SYS.TABLES WHERE 1 = 1 ${this.schemaFilterSql()} ORDER BY TABLE_NAME`);
813
+ return result.rows.map((row) => row.TABLE_NAME || row.table_name);
814
+ }
815
+ async showConstraints(tableName) {
816
+ const result = await this.query(`SELECT CONSTRAINT_NAME AS name, TABLE_NAME AS tableName, IS_PRIMARY_KEY AS isPrimaryKey, IS_UNIQUE_KEY AS isUniqueKey
817
+ FROM SYS.CONSTRAINTS
818
+ WHERE TABLE_NAME = '${tableName.toUpperCase()}' ${this.schemaFilterSql()}
819
+ ORDER BY CONSTRAINT_NAME`);
820
+ return result.rows;
821
+ }
822
+ async addConstraint(tableName, options) {
823
+ await this.createConstraint(tableName, {
824
+ name: options.name || `${tableName}_${options.type.toLowerCase().replace(/\s+/g, '_')}`,
825
+ type: options.type,
826
+ fields: options.fields,
827
+ references: options.references
828
+ ? { table: options.references.table, field: options.references.fields }
829
+ : undefined,
830
+ check: options.check,
831
+ });
832
+ }
833
+ async removeConstraint(tableName, constraintName) {
834
+ await this.dropConstraint(tableName, constraintName);
835
+ }
836
+ async showIndexes(tableName) {
837
+ const result = await this.query(`SELECT INDEX_NAME AS name, TABLE_NAME AS tableName, CONSTRAINT AS constraintType
838
+ FROM SYS.INDEXES
839
+ WHERE TABLE_NAME = '${tableName.toUpperCase()}' ${this.schemaFilterSql()}
840
+ ORDER BY INDEX_NAME`);
841
+ return result.rows;
842
+ }
843
+ // ==================== Index Operations ====================
844
+ buildCreateIndexSql(tableName, indexName, fields, options) {
845
+ const fieldsSql = (fields || []).map((f) => this.escapeId(f)).join(', ');
846
+ let sql = 'CREATE';
847
+ if (options?.unique)
848
+ sql += ' UNIQUE';
849
+ sql += ` INDEX ${this.escapeId(indexName)} ON ${this.quoteTable(tableName)} (${fieldsSql})`;
850
+ return sql;
851
+ }
852
+ async addIndex(tableName, indexName, fields, options) {
853
+ await this.query(this.buildCreateIndexSql(tableName, indexName, fields, options));
854
+ }
855
+ async removeIndex(_tableName, indexName) {
856
+ await this.query(`DROP INDEX ${this.escapeId(indexName)}`);
857
+ }
858
+ async createIndex(tableName, indexDef) {
859
+ await this.addIndex(tableName, indexDef.name, indexDef.fields, {
860
+ unique: indexDef.unique,
861
+ type: indexDef.type,
862
+ using: indexDef.using,
863
+ });
864
+ }
865
+ async dropIndex(tableName, indexName, _options) {
866
+ await this.removeIndex(tableName, indexName);
867
+ }
868
+ async createFulltextIndex(tableName, indexName, fields, _options) {
869
+ // HANA's full-text search is implemented via CREATE FULLTEXT INDEX,
870
+ // backed by the Full-Text Search Engine, and operates on a single
871
+ // NVARCHAR/NCLOB column at a time.
872
+ const field = fields[0];
873
+ await this.query(`CREATE FULLTEXT INDEX ${this.escapeId(indexName)} ON ${this.quoteTable(tableName)} (${this.escapeId(field)})`);
874
+ }
875
+ async createSpatialIndex(tableName, indexName, fields, _options) {
876
+ // HANA spatial indexes are created on an ST_GEOMETRY/ST_POINT column.
877
+ const field = fields[0];
878
+ await this.query(`CREATE SPATIAL INDEX ${this.escapeId(indexName)} ON ${this.quoteTable(tableName)} (${this.escapeId(field)})`);
879
+ }
880
+ async createPartialIndex() {
881
+ throw new Error('HANA does not support partial (filtered) indexes with a WHERE clause.');
882
+ }
883
+ async createExpressionIndex(tableName, indexName, expression, options) {
884
+ let sql = 'CREATE';
885
+ if (options?.unique)
886
+ sql += ' UNIQUE';
887
+ sql += ` INDEX ${this.escapeId(indexName)} ON ${this.quoteTable(tableName)} (${expression})`;
888
+ await this.query(sql);
889
+ }
890
+ // ==================== Constraint Operations ====================
891
+ async createConstraint(tableName, constraintDef) {
892
+ const constraintSql = this.buildConstraintSql({
893
+ name: constraintDef.name,
894
+ type: constraintDef.type,
895
+ fields: constraintDef.fields,
896
+ references: constraintDef.references
897
+ ? {
898
+ table: constraintDef.references.table,
899
+ field: constraintDef.references.field,
900
+ onDelete: constraintDef.references.onDelete,
901
+ onUpdate: constraintDef.references.onUpdate,
902
+ }
903
+ : undefined,
904
+ check: constraintDef.check,
905
+ });
906
+ await this.query(`ALTER TABLE ${this.quoteTable(tableName)} ADD ${constraintSql}`);
907
+ }
908
+ async dropConstraint(tableName, constraintName, _options) {
909
+ await this.query(`ALTER TABLE ${this.quoteTable(tableName)} DROP CONSTRAINT ${this.escapeId(constraintName)}`);
910
+ }
911
+ async changeOwner() {
912
+ throw new Error('HANA has no ALTER ... OWNER TO equivalent; ownership is managed via object privileges/roles instead.');
913
+ }
914
+ // ==================== Federation (Smart Data Access - out of scope) ====================
915
+ // HANA's real federation feature is Smart Data Access: `CREATE REMOTE
916
+ // SOURCE` registers a remote data source/adapter, and `CREATE VIRTUAL
917
+ // TABLE` maps a local object onto a remote table through it - conceptually
918
+ // similar to Postgres' FDW/foreign-server/foreign-table trio, but not a
919
+ // clean 1:1 mapping onto the generic (Postgres-shaped) FDW interface
920
+ // threaded through the shared Dialect type. These throw with a pointer to
921
+ // the real feature rather than emitting invalid SQL.
922
+ async createForeignDataWrapper() {
923
+ throw new Error('HANA has no FDW concept; use Smart Data Access (CREATE REMOTE SOURCE / CREATE VIRTUAL TABLE) instead.');
924
+ }
925
+ async dropForeignDataWrapper() {
926
+ throw new Error('HANA has no FDW concept; use Smart Data Access (CREATE REMOTE SOURCE / CREATE VIRTUAL TABLE) instead.');
927
+ }
928
+ async createForeignServer() {
929
+ throw new Error('HANA has no CREATE SERVER equivalent; use Smart Data Access (CREATE REMOTE SOURCE) instead.');
930
+ }
931
+ async dropForeignServer() {
932
+ throw new Error('HANA has no DROP SERVER equivalent; use Smart Data Access (DROP REMOTE SOURCE) instead.');
933
+ }
934
+ async createForeignTable() {
935
+ throw new Error('HANA has no CREATE FOREIGN TABLE equivalent; use Smart Data Access (CREATE VIRTUAL TABLE) instead.');
936
+ }
937
+ buildCreateServerQuery(_name, _opts) {
938
+ throw new Error('HANA has no FDW concept; use Smart Data Access (CREATE REMOTE SOURCE) instead.');
939
+ }
940
+ buildAlterServerQuery(_name, _opts) {
941
+ throw new Error('HANA has no FDW concept; use Smart Data Access (ALTER REMOTE SOURCE) instead.');
942
+ }
943
+ buildDropServerQuery(_name) {
944
+ throw new Error('HANA has no FDW concept; use Smart Data Access (DROP REMOTE SOURCE) instead.');
945
+ }
946
+ buildCreateUserMappingQuery(_opts) {
947
+ throw new Error('HANA has no user-mapping concept; Smart Data Access credentials are configured on the remote source itself.');
948
+ }
949
+ buildAlterUserMappingQuery(_opts) {
950
+ throw new Error('HANA has no user-mapping concept; Smart Data Access credentials are configured on the remote source itself.');
951
+ }
952
+ buildDropUserMappingQuery(_serverName, _user) {
953
+ throw new Error('HANA has no user-mapping concept; Smart Data Access credentials are configured on the remote source itself.');
954
+ }
955
+ async createUserMapping() {
956
+ throw new Error('HANA has no user-mapping concept; Smart Data Access credentials are configured on the remote source itself.');
957
+ }
958
+ async dropUserMapping() {
959
+ throw new Error('HANA has no user-mapping concept; Smart Data Access credentials are configured on the remote source itself.');
960
+ }
961
+ buildCreateForeignTableQuery(_tableName, _opts) {
962
+ throw new Error('HANA has no CREATE FOREIGN TABLE equivalent; use Smart Data Access (CREATE VIRTUAL TABLE) instead.');
963
+ }
964
+ buildDropForeignTableQuery(_tableName) {
965
+ throw new Error('HANA has no DROP FOREIGN TABLE equivalent; use Smart Data Access (DROP VIRTUAL TABLE) instead.');
966
+ }
967
+ buildImportForeignSchemaQuery(_remoteSchema, _serverName, _opts) {
968
+ throw new Error('HANA has no IMPORT FOREIGN SCHEMA equivalent; virtual tables must be created individually.');
969
+ }
970
+ getServersQuery() {
971
+ // Smart Data Access remote sources, for reference/parity even though
972
+ // the FDW-shaped methods above throw.
973
+ return 'SELECT REMOTE_SOURCE_NAME, ADAPTER_NAME FROM SYS.REMOTE_SOURCES ORDER BY REMOTE_SOURCE_NAME';
974
+ }
975
+ // ==================== Security Policies (Row-level security) ====================
976
+ // HANA has no simple predicate-based row-level-security policy statement
977
+ // like MSSQL/Postgres RLS; row-level restrictions are implemented via
978
+ // Structured Privileges / Analytic Privileges, which are administrative
979
+ // objects rather than plain SQL DDL suitable for this generic interface.
980
+ async createSecurityPolicy() {
981
+ throw new Error('HANA has no CREATE SECURITY POLICY equivalent; use Structured/Analytic Privileges instead.');
982
+ }
983
+ async dropSecurityPolicy() {
984
+ throw new Error('HANA has no CREATE SECURITY POLICY equivalent; use Structured/Analytic Privileges instead.');
985
+ }
986
+ // ==================== Transaction Operations ====================
987
+ async startTransaction(options) {
988
+ const client = this.createClient();
989
+ await this.connectClient(client);
990
+ client.setAutoCommit(false);
991
+ if (options?.isolationLevel) {
992
+ // HANA sets the isolation level for the *next* transaction via
993
+ // `SET TRANSACTION ISOLATION LEVEL ...` on the session, executed here
994
+ // before any statements run on the dedicated transaction connection.
995
+ await this.executeOnClient(client, `SET TRANSACTION ISOLATION LEVEL ${options.isolationLevel}`, []);
996
+ }
997
+ return new HanaTransaction(client, options);
998
+ }
999
+ async commitTransaction(transaction) {
1000
+ await transaction.commit();
1001
+ }
1002
+ async rollbackTransaction(transaction) {
1003
+ await transaction.rollback();
1004
+ }
1005
+ // ==================== Data Type Mapping ====================
1006
+ getDataTypeSql(dataType) {
1007
+ if (typeof dataType === 'string') {
1008
+ return this.mapDataType(dataType, undefined);
1009
+ }
1010
+ const dt = dataType;
1011
+ return this.mapDataType(String(dt.key ?? dataType), dt);
1012
+ }
1013
+ mapDataType(typeKey, dt) {
1014
+ switch (typeKey.toUpperCase()) {
1015
+ case 'STRING':
1016
+ if (dt?.length === 'max')
1017
+ return 'NCLOB';
1018
+ return `NVARCHAR(${String(dt?.length || 255)})`;
1019
+ case 'NVARCHAR':
1020
+ return `NVARCHAR(${String(dt?.length || 255)})`;
1021
+ case 'CHAR':
1022
+ return `NCHAR(${String(dt?.length || 1)})`;
1023
+ case 'SHORTTEXT':
1024
+ return `SHORTTEXT(${String(dt?.length || 255)})`;
1025
+ case 'ALPHANUM':
1026
+ return `ALPHANUM(${String(dt?.length || 10)})`;
1027
+ case 'TEXT':
1028
+ return 'NCLOB';
1029
+ case 'TINYTEXT':
1030
+ return 'NVARCHAR(255)';
1031
+ case 'MEDIUMTEXT':
1032
+ case 'LONGTEXT':
1033
+ return 'NCLOB';
1034
+ case 'INTEGER': {
1035
+ if (dt?.length === 1)
1036
+ return 'TINYINT';
1037
+ if (dt?.length === 2)
1038
+ return 'SMALLINT';
1039
+ if (dt?.length === 8)
1040
+ return 'BIGINT';
1041
+ return 'INTEGER';
1042
+ }
1043
+ case 'TINYINT':
1044
+ return 'TINYINT';
1045
+ case 'BIGINT':
1046
+ return 'BIGINT';
1047
+ case 'FLOAT':
1048
+ return 'REAL';
1049
+ case 'DOUBLE':
1050
+ return 'DOUBLE';
1051
+ case 'NUMBER':
1052
+ case 'DECIMAL':
1053
+ case 'NUMERIC':
1054
+ return `DECIMAL(${String(dt?.precision || 10)}, ${String(dt?.scale || 0)})`;
1055
+ case 'BOOLEAN':
1056
+ return 'BOOLEAN';
1057
+ case 'DATE':
1058
+ case 'DATETIME':
1059
+ case 'TIMESTAMP':
1060
+ return 'TIMESTAMP';
1061
+ case 'SECONDDATE':
1062
+ return 'SECONDDATE';
1063
+ case 'DATEONLY':
1064
+ return 'DATE';
1065
+ case 'TIME':
1066
+ return 'TIME';
1067
+ case 'BLOB':
1068
+ return 'BLOB';
1069
+ case 'BINARY':
1070
+ case 'VARBINARY':
1071
+ return `VARBINARY(${String(dt?.length || 255)})`;
1072
+ case 'BIT':
1073
+ return 'BOOLEAN';
1074
+ case 'JSON':
1075
+ return 'NCLOB';
1076
+ case 'JSONB':
1077
+ return 'BLOB';
1078
+ case 'UUID':
1079
+ return 'NVARCHAR(36)';
1080
+ case 'GEOMETRY':
1081
+ return dt?.srid ? `ST_GEOMETRY(${String(dt.srid)})` : 'ST_GEOMETRY';
1082
+ case 'ST_POINT':
1083
+ return dt?.srid ? `ST_POINT(${String(dt.srid)})` : 'ST_POINT';
1084
+ case 'ENUM':
1085
+ return 'NVARCHAR(255)';
1086
+ case 'VIRTUAL':
1087
+ return '';
1088
+ default:
1089
+ // Pass through unrecognized (often HANA-native) type strings as-is
1090
+ // rather than defaulting to a generic string type - e.g. callers may
1091
+ // legitimately pass 'SECONDDATE', 'ALPHANUM(10)' etc directly.
1092
+ return typeKey;
1093
+ }
1094
+ }
1095
+ // ==================== Query Builders ====================
1096
+ buildWhereClause(where, _options) {
1097
+ const values = [];
1098
+ if (!where || Object.keys(where).length === 0) {
1099
+ return { sql: '', values };
1100
+ }
1101
+ const buildCondition = (condition) => {
1102
+ if (!condition || typeof condition !== 'object') {
1103
+ values.push(condition);
1104
+ return '?';
1105
+ }
1106
+ const cond = condition;
1107
+ if (cond.$and || cond.$or || cond.$not) {
1108
+ const conditions = [];
1109
+ if (cond.$and) {
1110
+ conditions.push(`(${cond.$and.map((c) => buildCondition(c)).join(' AND ')})`);
1111
+ }
1112
+ if (cond.$or) {
1113
+ conditions.push(`(${cond.$or.map((c) => buildCondition(c)).join(' OR ')})`);
1114
+ }
1115
+ if (cond.$not) {
1116
+ conditions.push(`NOT (${buildCondition(cond.$not)})`);
1117
+ }
1118
+ return conditions.join(' AND ');
1119
+ }
1120
+ const fieldConditions = [];
1121
+ for (const [key, value] of Object.entries(cond)) {
1122
+ if (key.startsWith('$'))
1123
+ continue;
1124
+ if (value && typeof value === 'object' && !(value instanceof Date) && !Array.isArray(value)) {
1125
+ const valueObj = value;
1126
+ if (valueObj.$eq !== undefined) {
1127
+ values.push(valueObj.$eq);
1128
+ fieldConditions.push(`${this.escapeId(key)} = ?`);
1129
+ }
1130
+ else if (valueObj.$ne !== undefined) {
1131
+ values.push(valueObj.$ne);
1132
+ fieldConditions.push(`${this.escapeId(key)} != ?`);
1133
+ }
1134
+ else if (valueObj.$gt !== undefined) {
1135
+ values.push(valueObj.$gt);
1136
+ fieldConditions.push(`${this.escapeId(key)} > ?`);
1137
+ }
1138
+ else if (valueObj.$gte !== undefined) {
1139
+ values.push(valueObj.$gte);
1140
+ fieldConditions.push(`${this.escapeId(key)} >= ?`);
1141
+ }
1142
+ else if (valueObj.$lt !== undefined) {
1143
+ values.push(valueObj.$lt);
1144
+ fieldConditions.push(`${this.escapeId(key)} < ?`);
1145
+ }
1146
+ else if (valueObj.$lte !== undefined) {
1147
+ values.push(valueObj.$lte);
1148
+ fieldConditions.push(`${this.escapeId(key)} <= ?`);
1149
+ }
1150
+ else if (valueObj.$like !== undefined) {
1151
+ values.push(valueObj.$like);
1152
+ fieldConditions.push(`${this.escapeId(key)} LIKE ?`);
1153
+ }
1154
+ else if (valueObj.$notLike !== undefined) {
1155
+ values.push(valueObj.$notLike);
1156
+ fieldConditions.push(`${this.escapeId(key)} NOT LIKE ?`);
1157
+ }
1158
+ else if (valueObj.$in) {
1159
+ const inValues = valueObj.$in;
1160
+ values.push(...inValues);
1161
+ fieldConditions.push(`${this.escapeId(key)} IN (${inValues.map(() => '?').join(', ')})`);
1162
+ }
1163
+ else if (valueObj.$notIn) {
1164
+ const notInValues = valueObj.$notIn;
1165
+ values.push(...notInValues);
1166
+ fieldConditions.push(`${this.escapeId(key)} NOT IN (${notInValues.map(() => '?').join(', ')})`);
1167
+ }
1168
+ else if (valueObj.$between) {
1169
+ const between = valueObj.$between;
1170
+ values.push(between[0], between[1]);
1171
+ fieldConditions.push(`${this.escapeId(key)} BETWEEN ? AND ?`);
1172
+ }
1173
+ else if (valueObj.$notBetween) {
1174
+ const notBetween = valueObj.$notBetween;
1175
+ values.push(notBetween[0], notBetween[1]);
1176
+ fieldConditions.push(`${this.escapeId(key)} NOT BETWEEN ? AND ?`);
1177
+ }
1178
+ else if (valueObj.$isNull !== undefined) {
1179
+ fieldConditions.push(valueObj.$isNull ? `${this.escapeId(key)} IS NULL` : `${this.escapeId(key)} IS NOT NULL`);
1180
+ }
1181
+ else {
1182
+ values.push(value);
1183
+ fieldConditions.push(`${this.escapeId(key)} = ?`);
1184
+ }
1185
+ }
1186
+ else if (value === null) {
1187
+ fieldConditions.push(`${this.escapeId(key)} IS NULL`);
1188
+ }
1189
+ else if (Array.isArray(value)) {
1190
+ values.push(...value);
1191
+ fieldConditions.push(`${this.escapeId(key)} IN (${value.map(() => '?').join(', ')})`);
1192
+ }
1193
+ else {
1194
+ values.push(value);
1195
+ fieldConditions.push(`${this.escapeId(key)} = ?`);
1196
+ }
1197
+ }
1198
+ return fieldConditions.join(' AND ');
1199
+ };
1200
+ const sql = buildCondition(where);
1201
+ return { sql, values };
1202
+ }
1203
+ buildOrderClause(order, _options) {
1204
+ const orderArray = order;
1205
+ if (!orderArray || !Array.isArray(orderArray) || orderArray.length === 0) {
1206
+ return '';
1207
+ }
1208
+ const orderParts = [];
1209
+ for (const item of orderArray) {
1210
+ if (Array.isArray(item)) {
1211
+ const field = typeof item[0] === 'string' ? this.escapeId(item[0]) : item[0];
1212
+ const direction = item[1] ? ` ${item[1]}` : '';
1213
+ orderParts.push(`${field}${direction}`);
1214
+ }
1215
+ else if (typeof item === 'string') {
1216
+ orderParts.push(this.escapeId(item));
1217
+ }
1218
+ }
1219
+ return orderParts.length > 0 ? `ORDER BY ${orderParts.join(', ')}` : '';
1220
+ }
1221
+ /**
1222
+ * HANA pagination uses the standard `LIMIT n OFFSET m` clause (unlike
1223
+ * Oracle/Db2's `OFFSET ... FETCH FIRST ...`). Unlike PostgreSQL/MySQL,
1224
+ * HANA does not allow a bare `OFFSET` without a `LIMIT` - when only an
1225
+ * offset is supplied, a sentinel maximum `LIMIT` is emitted alongside it.
1226
+ */
1227
+ buildLimitOffset(limit, offset) {
1228
+ if (limit === undefined && offset === undefined)
1229
+ return '';
1230
+ const limitValue = limit !== undefined ? Number(limit) : HANA_MAX_LIMIT;
1231
+ let sql = ` LIMIT ${limitValue}`;
1232
+ if (offset !== undefined) {
1233
+ sql += ` OFFSET ${Number(offset)}`;
1234
+ }
1235
+ return sql;
1236
+ }
1237
+ buildInsertQuery(tableName, values, options) {
1238
+ const columns = Object.keys(values);
1239
+ const processedValues = [];
1240
+ const placeholders = [];
1241
+ for (const value of Object.values(values)) {
1242
+ if (value instanceof prorm_1.Literal) {
1243
+ placeholders.push(value.val);
1244
+ }
1245
+ else {
1246
+ placeholders.push('?');
1247
+ processedValues.push(value);
1248
+ }
1249
+ }
1250
+ const sql = `INSERT INTO ${this.quoteTable(tableName, options?.schema)} (${columns.map((c) => this.escapeId(c)).join(', ')}) VALUES (${placeholders.join(', ')})`;
1251
+ if (options?.returning) {
1252
+ // HANA has no INSERT ... RETURNING clause; callers must re-select the
1253
+ // row after the insert (e.g. by identity value or supplied key).
1254
+ throw new Error('HANA does not support a RETURNING clause on INSERT. Re-fetch the row after the insert instead.');
1255
+ }
1256
+ return { sql, values: processedValues };
1257
+ }
1258
+ buildUpdateQuery(tableName, values, where, options) {
1259
+ const processedValues = [];
1260
+ const setClauses = [];
1261
+ for (const [key, value] of Object.entries(values)) {
1262
+ if (value instanceof prorm_1.Literal) {
1263
+ setClauses.push(`${this.escapeId(key)} = ${value.val}`);
1264
+ }
1265
+ else {
1266
+ setClauses.push(`${this.escapeId(key)} = ?`);
1267
+ processedValues.push(value);
1268
+ }
1269
+ }
1270
+ let sql = `UPDATE ${this.quoteTable(tableName)} SET ${setClauses.join(', ')}`;
1271
+ const whereClause = this.buildWhereClause(where);
1272
+ if (whereClause.sql) {
1273
+ sql += ` WHERE ${whereClause.sql}`;
1274
+ processedValues.push(...whereClause.values);
1275
+ }
1276
+ if (options?.returning) {
1277
+ throw new Error('HANA does not support a RETURNING clause on UPDATE. Re-fetch the row after the update instead.');
1278
+ }
1279
+ return { sql, values: processedValues };
1280
+ }
1281
+ buildDeleteQuery(tableName, where, options) {
1282
+ if (options?.truncate) {
1283
+ return { sql: `TRUNCATE TABLE ${this.quoteTable(tableName)}`, values: [] };
1284
+ }
1285
+ let sql = `DELETE FROM ${this.quoteTable(tableName)}`;
1286
+ const processedValues = [];
1287
+ const whereClause = this.buildWhereClause(where);
1288
+ if (whereClause.sql) {
1289
+ sql += ` WHERE ${whereClause.sql}`;
1290
+ processedValues.push(...whereClause.values);
1291
+ }
1292
+ if (options?.returning) {
1293
+ throw new Error('HANA does not support a RETURNING clause on DELETE. Re-fetch/capture the row before deleting instead.');
1294
+ }
1295
+ return { sql, values: processedValues };
1296
+ }
1297
+ /**
1298
+ * Build a `WITH name(columns) AS (base UNION ALL recursive)` prefix for a
1299
+ * recursive common table expression. HANA has full standard recursive CTE
1300
+ * support since SPS09.
1301
+ */
1302
+ buildRecursiveCteClause(cte) {
1303
+ const columnList = cte.columns && cte.columns.length > 0
1304
+ ? `(${cte.columns.map((c) => this.escapeId(c)).join(', ')})`
1305
+ : '';
1306
+ const unionKeyword = cte.unionAll === false ? 'UNION' : 'UNION ALL';
1307
+ return `WITH ${this.escapeId(cte.name)}${columnList} AS (${cte.baseQuery} ${unionKeyword} ${cte.recursiveQuery})`;
1308
+ }
1309
+ buildRecursiveCteQuery(cte, outer) {
1310
+ return this.buildSelectQuery({
1311
+ ...outer,
1312
+ tableName: outer.tableName || cte.name,
1313
+ cte,
1314
+ });
1315
+ }
1316
+ /**
1317
+ * Build an OLAP window/analytic function expression, e.g.:
1318
+ * ROW_NUMBER() OVER (PARTITION BY "dept" ORDER BY "salary" DESC)
1319
+ *
1320
+ * HANA has mature, standard window-function support.
1321
+ */
1322
+ buildWindowFunction(options) {
1323
+ const args = (options.args || []).join(', ');
1324
+ const overParts = [];
1325
+ if (options.partitionBy) {
1326
+ const cols = Array.isArray(options.partitionBy) ? options.partitionBy : [options.partitionBy];
1327
+ overParts.push(`PARTITION BY ${cols.map((c) => this.escapeId(c)).join(', ')}`);
1328
+ }
1329
+ if (options.orderBy && options.orderBy.length > 0) {
1330
+ const orderParts = options.orderBy.map((o) => typeof o === 'string' ? this.escapeId(o) : `${this.escapeId(o.column)}${o.direction ? ` ${o.direction}` : ''}`);
1331
+ overParts.push(`ORDER BY ${orderParts.join(', ')}`);
1332
+ }
1333
+ if (options.frame) {
1334
+ overParts.push(options.frame);
1335
+ }
1336
+ return `${options.fn.toUpperCase()}(${args}) OVER (${overParts.join(' ')})`;
1337
+ }
1338
+ buildSelectQuery(options) {
1339
+ const values = [];
1340
+ const cte = options.cte;
1341
+ let sql = cte ? `${this.buildRecursiveCteClause(cte)} ` : '';
1342
+ sql += 'SELECT ';
1343
+ if (options.distinct) {
1344
+ sql += 'DISTINCT ';
1345
+ }
1346
+ if (options.attributes) {
1347
+ if (Array.isArray(options.attributes)) {
1348
+ sql += options.attributes
1349
+ .map((a) => {
1350
+ if (a === '*')
1351
+ return a;
1352
+ if (typeof a === 'object' && a !== null && 'window' in a) {
1353
+ const windowAttr = a;
1354
+ return `${this.buildWindowFunction(windowAttr.window)} AS ${this.escapeId(windowAttr.as)}`;
1355
+ }
1356
+ return this.escapeId(a);
1357
+ })
1358
+ .join(', ');
1359
+ }
1360
+ else {
1361
+ const include = options.attributes.include?.map((a) => this.escapeId(a)).join(', ');
1362
+ sql += include || '*';
1363
+ }
1364
+ }
1365
+ else {
1366
+ sql += '*';
1367
+ }
1368
+ sql += ` FROM ${this.quoteTable(options.tableName, options.schema)}`;
1369
+ if (options.include && options.include.length > 0) {
1370
+ for (const include of options.include) {
1371
+ const joinType = include.required ? 'INNER JOIN' : 'LEFT JOIN';
1372
+ const joinModel = include.model;
1373
+ const joinTableName = joinModel.tableName || joinModel.name || '';
1374
+ const joinAlias = include.as || joinTableName;
1375
+ sql += ` ${joinType} ${this.quoteTable(joinTableName)} AS ${this.escapeId(joinAlias)}`;
1376
+ if (include.on) {
1377
+ const onClause = this.buildWhereClause(include.on);
1378
+ sql += ` ON ${onClause.sql}`;
1379
+ values.push(...onClause.values);
1380
+ }
1381
+ }
1382
+ }
1383
+ if (options.where && Object.keys(options.where).length > 0) {
1384
+ const whereClause = this.buildWhereClause(options.where);
1385
+ if (whereClause.sql) {
1386
+ sql += ` WHERE ${whereClause.sql}`;
1387
+ values.push(...whereClause.values);
1388
+ }
1389
+ }
1390
+ if (options.group) {
1391
+ const groupBy = Array.isArray(options.group) ? options.group : [options.group];
1392
+ sql += ` GROUP BY ${groupBy.map((g) => this.escapeId(g)).join(', ')}`;
1393
+ if (options.having && Object.keys(options.having).length > 0) {
1394
+ const havingClause = this.buildWhereClause(options.having);
1395
+ sql += ` HAVING ${havingClause.sql}`;
1396
+ values.push(...havingClause.values);
1397
+ }
1398
+ }
1399
+ if (options.order) {
1400
+ const orderClause = this.buildOrderClause(options.order);
1401
+ if (orderClause)
1402
+ sql += ` ${orderClause}`;
1403
+ }
1404
+ if (options.limit !== undefined || options.offset !== undefined) {
1405
+ sql += this.buildLimitOffset(options.limit, options.offset);
1406
+ }
1407
+ if (options.lock) {
1408
+ sql += ' FOR UPDATE';
1409
+ }
1410
+ return { sql, values };
1411
+ }
1412
+ /**
1413
+ * Build an UPSERT query using HANA's native `UPSERT ... WITH PRIMARY KEY`
1414
+ * statement - a genuine HANA-only construct (distinct from the `MERGE
1415
+ * INTO` syntax every other enterprise dialect in this codebase uses for
1416
+ * upserts) that inserts a row or updates it in place when a row with a
1417
+ * matching primary key already exists. When `conflictFields` names
1418
+ * columns other than the primary key, a `MERGE INTO` statement is emitted
1419
+ * instead, since `UPSERT ... WITH PRIMARY KEY` can only match on the
1420
+ * table's actual primary key.
1421
+ */
1422
+ buildUpsertQuery(tableName, values, options) {
1423
+ const columns = Object.keys(values);
1424
+ const queryValues = Object.values(values);
1425
+ if (!options?.conflictFields || options.conflictFields.length === 0) {
1426
+ const sourceColumns = columns.map((c) => this.escapeId(c)).join(', ');
1427
+ const placeholders = columns.map(() => '?').join(', ');
1428
+ const sql = `UPSERT ${this.quoteTable(tableName, options?.schema)} (${sourceColumns}) VALUES (${placeholders}) WITH PRIMARY KEY`;
1429
+ return { sql, values: queryValues };
1430
+ }
1431
+ // Explicit conflict target: fall back to MERGE INTO, which can match on
1432
+ // any column set, not just the primary key.
1433
+ const conflictFields = options.conflictFields;
1434
+ const updateFields = options.updateOnDuplicate && options.updateOnDuplicate.length > 0
1435
+ ? options.updateOnDuplicate
1436
+ : columns.filter((c) => !conflictFields.includes(c));
1437
+ const sourceColumns = columns.map((c) => this.escapeId(c)).join(', ');
1438
+ const sourceValues = columns.map(() => '?').join(', ');
1439
+ const onClauses = conflictFields
1440
+ .map((c) => `target.${this.escapeId(c)} = source.${this.escapeId(c)}`)
1441
+ .join(' AND ');
1442
+ const updateSet = updateFields.length > 0
1443
+ ? updateFields.map((c) => `${this.escapeId(c)} = source.${this.escapeId(c)}`).join(', ')
1444
+ : columns.map((c) => `${this.escapeId(c)} = source.${this.escapeId(c)}`).join(', ');
1445
+ const sql = `MERGE INTO ${this.quoteTable(tableName, options?.schema)} AS target ` +
1446
+ `USING (SELECT ${columns.map((c, i) => `? AS ${this.escapeId(c)}`).join(', ')} FROM DUMMY) AS source ` +
1447
+ `ON ${onClauses} ` +
1448
+ `WHEN MATCHED THEN UPDATE SET ${updateSet} ` +
1449
+ `WHEN NOT MATCHED THEN INSERT (${sourceColumns}) VALUES (${columns.map((c) => `source.${this.escapeId(c)}`).join(', ')})`;
1450
+ void sourceValues;
1451
+ return { sql, values: queryValues };
1452
+ }
1453
+ buildIncrementQuery(tableName, fields, where, options) {
1454
+ const by = options?.by ?? 1;
1455
+ const setClauses = [];
1456
+ const queryValues = [];
1457
+ if (typeof fields === 'string') {
1458
+ setClauses.push(`${this.escapeId(fields)} = ${this.escapeId(fields)} + ?`);
1459
+ queryValues.push(by);
1460
+ }
1461
+ else if (Array.isArray(fields)) {
1462
+ for (const field of fields) {
1463
+ setClauses.push(`${this.escapeId(field)} = ${this.escapeId(field)} + ?`);
1464
+ queryValues.push(by);
1465
+ }
1466
+ }
1467
+ else {
1468
+ for (const [field, value] of Object.entries(fields)) {
1469
+ setClauses.push(`${this.escapeId(field)} = ${this.escapeId(field)} + ?`);
1470
+ queryValues.push(value);
1471
+ }
1472
+ }
1473
+ let sql = `UPDATE ${this.quoteTable(tableName)} SET ${setClauses.join(', ')}`;
1474
+ const whereClause = this.buildWhereClause(where);
1475
+ if (whereClause.sql) {
1476
+ sql += ` WHERE ${whereClause.sql}`;
1477
+ queryValues.push(...whereClause.values);
1478
+ }
1479
+ return { sql, values: queryValues };
1480
+ }
1481
+ replaceReplacements(sql, replacements) {
1482
+ if (!replacements)
1483
+ return sql;
1484
+ if (Array.isArray(replacements)) {
1485
+ let result = sql;
1486
+ for (const value of replacements) {
1487
+ result = result.replace('?', this.escape(value));
1488
+ }
1489
+ return result;
1490
+ }
1491
+ let result = sql;
1492
+ for (const [key, value] of Object.entries(replacements)) {
1493
+ result = result.replace(new RegExp(`:${key}\\b`, 'g'), this.escape(value));
1494
+ }
1495
+ return result;
1496
+ }
1497
+ // ==================== User / Privilege Management ====================
1498
+ // Unlike Db2 (OS/LDAP-authenticated only), HANA has real, native SQL user
1499
+ // and role management.
1500
+ buildCreateUserQuery(username, options) {
1501
+ let sql = `CREATE USER ${this.escapeId(username)}`;
1502
+ if (options?.password) {
1503
+ sql += ` PASSWORD "${options.password.replace(/"/g, '""')}"`;
1504
+ }
1505
+ if (options?.forceFirstPasswordChange === false) {
1506
+ sql += ' NO FORCE_FIRST_PASSWORD_CHANGE';
1507
+ }
1508
+ return sql;
1509
+ }
1510
+ buildAlterUserQuery(username, options) {
1511
+ let sql = `ALTER USER ${this.escapeId(username)}`;
1512
+ if (options?.password) {
1513
+ sql += ` PASSWORD "${options.password.replace(/"/g, '""')}"`;
1514
+ }
1515
+ return sql;
1516
+ }
1517
+ buildDropUserQuery(username) {
1518
+ return `DROP USER ${this.escapeId(username)}`;
1519
+ }
1520
+ getUsersQuery() {
1521
+ return 'SELECT USER_NAME FROM SYS.USERS ORDER BY USER_NAME';
1522
+ }
1523
+ buildGrantQuery(options) {
1524
+ const privileges = Array.isArray(options.privileges) ? options.privileges.join(', ') : options.privileges;
1525
+ let sql = `GRANT ${privileges}`;
1526
+ if (options.on)
1527
+ sql += ` ON ${options.on}`;
1528
+ sql += ` TO ${this.escapeId(options.to)}`;
1529
+ return sql;
1530
+ }
1531
+ buildRevokeQuery(options) {
1532
+ const privileges = Array.isArray(options.privileges) ? options.privileges.join(', ') : options.privileges;
1533
+ let sql = `REVOKE ${privileges}`;
1534
+ if (options.on)
1535
+ sql += ` ON ${options.on}`;
1536
+ sql += ` FROM ${this.escapeId(options.from)}`;
1537
+ return sql;
1538
+ }
1539
+ buildShowGrantsQuery(username) {
1540
+ return `SELECT * FROM SYS.GRANTED_PRIVILEGES WHERE GRANTEE = '${username.toUpperCase()}'`;
1541
+ }
1542
+ buildFlushPrivilegesQuery() {
1543
+ throw new Error('HANA applies privilege changes immediately; there is no FLUSH PRIVILEGES equivalent.');
1544
+ }
1545
+ buildCreateRoleQuery(roleName) {
1546
+ return `CREATE ROLE ${this.escapeId(roleName)}`;
1547
+ }
1548
+ buildDropRoleQuery(roleName) {
1549
+ return `DROP ROLE ${this.escapeId(roleName)}`;
1550
+ }
1551
+ buildGrantRoleQuery(role, to) {
1552
+ return `GRANT ${this.escapeId(role)} TO ${this.escapeId(to)}`;
1553
+ }
1554
+ buildRevokeRoleQuery(role, from) {
1555
+ return `REVOKE ${this.escapeId(role)} FROM ${this.escapeId(from)}`;
1556
+ }
1557
+ getRolesQuery() {
1558
+ return 'SELECT ROLE_NAME FROM SYS.ROLES ORDER BY ROLE_NAME';
1559
+ }
1560
+ // ==================== Sequences ====================
1561
+ async createSequence(options) {
1562
+ const seqName = options.schema
1563
+ ? `${this.escapeId(options.schema)}.${this.escapeId(options.name)}`
1564
+ : this.escapeId(options.name);
1565
+ if (options.ifNotExists) {
1566
+ const exists = await this.hasSequence(options.name, options.schema);
1567
+ if (exists)
1568
+ return;
1569
+ }
1570
+ let sql = `CREATE SEQUENCE ${seqName}`;
1571
+ if (options.startWith !== undefined)
1572
+ sql += ` START WITH ${options.startWith}`;
1573
+ if (options.incrementBy !== undefined)
1574
+ sql += ` INCREMENT BY ${options.incrementBy}`;
1575
+ if (options.minvalue !== undefined)
1576
+ sql += ` MINVALUE ${options.minvalue}`;
1577
+ if (options.maxvalue !== undefined)
1578
+ sql += ` MAXVALUE ${options.maxvalue}`;
1579
+ sql += options.cycle ? ' CYCLE' : ' NO CYCLE';
1580
+ await this.query(sql);
1581
+ }
1582
+ async dropSequence(sequenceName, options) {
1583
+ const seqName = options?.schema
1584
+ ? `${this.escapeId(options.schema)}.${this.escapeId(sequenceName)}`
1585
+ : this.escapeId(sequenceName);
1586
+ if (options?.ifExists) {
1587
+ const exists = await this.hasSequence(sequenceName, options.schema);
1588
+ if (!exists)
1589
+ return;
1590
+ }
1591
+ await this.query(`DROP SEQUENCE ${seqName}`);
1592
+ }
1593
+ /**
1594
+ * Get the next value of a sequence via HANA's Oracle-style pseudo-column
1595
+ * access (`<sequence>.NEXTVAL`), selected from the single-row `DUMMY`
1596
+ * pseudo-table (HANA's equivalent of Oracle's `DUAL`).
1597
+ */
1598
+ async nextSequenceValue(sequenceName) {
1599
+ const result = await this.query(`SELECT ${this.escapeId(sequenceName)}.NEXTVAL AS NEXTVAL FROM DUMMY`);
1600
+ const row = result.rows[0];
1601
+ return Number(row?.NEXTVAL ?? row?.nextval);
1602
+ }
1603
+ async currSequenceValue(sequenceName) {
1604
+ const result = await this.query(`SELECT ${this.escapeId(sequenceName)}.CURRVAL AS CURRVAL FROM DUMMY`);
1605
+ const row = result.rows[0];
1606
+ return Number(row?.CURRVAL ?? row?.currval);
1607
+ }
1608
+ async hasSequence(sequenceName, schema) {
1609
+ let sql = `SELECT SEQUENCE_NAME FROM SYS.SEQUENCES WHERE SEQUENCE_NAME = '${sequenceName.toUpperCase()}'`;
1610
+ if (schema)
1611
+ sql += ` AND SCHEMA_NAME = '${schema.toUpperCase()}'`;
1612
+ const result = await this.query(sql);
1613
+ return result.rows.length > 0;
1614
+ }
1615
+ async listSequences() {
1616
+ const result = await this.query('SELECT SCHEMA_NAME, SEQUENCE_NAME FROM SYS.SEQUENCES ORDER BY SCHEMA_NAME, SEQUENCE_NAME');
1617
+ return result.rows.map((row) => `${row.SCHEMA_NAME || row.schema_name}.${row.SEQUENCE_NAME || row.sequence_name}`);
1618
+ }
1619
+ // ==================== Identity & Computed Columns ====================
1620
+ async createIdentityColumn(tableName, columnName, options) {
1621
+ const startWith = options?.startWith ?? 1;
1622
+ const incrementBy = options?.incrementBy ?? 1;
1623
+ await this.query(`ALTER TABLE ${this.quoteTable(tableName)} ALTER (${this.escapeId(columnName)} GENERATED ALWAYS AS IDENTITY (START WITH ${startWith} INCREMENT BY ${incrementBy}))`);
1624
+ }
1625
+ /** HANA calculated (computed) columns: `col type GENERATED ALWAYS AS (expr)`. */
1626
+ async createComputedColumn(tableName, columnName, expression, options) {
1627
+ const type = options?.type || 'NVARCHAR(255)';
1628
+ const persisted = options?.persisted ? ' PERSISTED' : '';
1629
+ await this.query(`ALTER TABLE ${this.quoteTable(tableName)} ADD (${this.escapeId(columnName)} ${type} GENERATED ALWAYS AS (${expression})${persisted})`);
1630
+ }
1631
+ // ==================== Stored Procedures ====================
1632
+ // HANA supports both SQLScript stored procedures and functions; only
1633
+ // procedures are modeled here per the shared StoredProcedureOptions shape.
1634
+ async createStoredProcedure(options) {
1635
+ const procName = options.schema
1636
+ ? `${this.escapeId(options.schema)}.${this.escapeId(options.name)}`
1637
+ : this.escapeId(options.name);
1638
+ if (options.ifNotExists && !options.replace) {
1639
+ const exists = await this.hasStoredProcedure(options.name, options.schema);
1640
+ if (exists)
1641
+ return;
1642
+ }
1643
+ const paramList = (options.params || [])
1644
+ .map((param) => {
1645
+ const dir = param.mode === 'OUT' ? 'OUT' : param.mode === 'INOUT' ? 'INOUT' : 'IN';
1646
+ return `${dir} ${this.escapeId(param.name)} ${param.type}`;
1647
+ })
1648
+ .join(', ');
1649
+ const replace = options.replace ? 'OR REPLACE ' : '';
1650
+ const sql = `CREATE ${replace}PROCEDURE ${procName} (${paramList})\nLANGUAGE SQLSCRIPT\nSQL SECURITY INVOKER\nAS\nBEGIN\n${options.body}\nEND`;
1651
+ await this.query(sql);
1652
+ }
1653
+ async createProcedure(options) {
1654
+ return this.createStoredProcedure(options);
1655
+ }
1656
+ async dropStoredProcedure(procedureName, options) {
1657
+ const procName = options?.schema
1658
+ ? `${this.escapeId(options.schema)}.${this.escapeId(procedureName)}`
1659
+ : this.escapeId(procedureName);
1660
+ if (options?.ifExists) {
1661
+ const exists = await this.hasStoredProcedure(procedureName, options.schema);
1662
+ if (!exists)
1663
+ return;
1664
+ }
1665
+ await this.query(`DROP PROCEDURE ${procName}`);
1666
+ }
1667
+ async dropProcedure(procedureName, options) {
1668
+ return this.dropStoredProcedure(procedureName, options);
1669
+ }
1670
+ async executeStoredProcedure(options) {
1671
+ const procName = options.schema
1672
+ ? `${this.escapeId(options.schema)}.${this.escapeId(options.procedureName)}`
1673
+ : this.escapeId(options.procedureName);
1674
+ const paramValues = options.params ? Object.values(options.params) : [];
1675
+ const placeholders = paramValues.map(() => '?').join(', ');
1676
+ const sql = `CALL ${procName}(${placeholders})`;
1677
+ return this.query(sql, { replacements: paramValues });
1678
+ }
1679
+ async hasStoredProcedure(procedureName, schema) {
1680
+ let sql = `SELECT PROCEDURE_NAME FROM SYS.PROCEDURES WHERE PROCEDURE_NAME = '${procedureName.toUpperCase()}'`;
1681
+ if (schema)
1682
+ sql += ` AND SCHEMA_NAME = '${schema.toUpperCase()}'`;
1683
+ const result = await this.query(sql);
1684
+ return result.rows.length > 0;
1685
+ }
1686
+ // ==================== Triggers ====================
1687
+ async createTrigger(options) {
1688
+ const triggerName = options.schema
1689
+ ? `${this.escapeId(options.schema)}.${this.escapeId(options.name)}`
1690
+ : this.escapeId(options.name);
1691
+ const tableName = options.schema
1692
+ ? `${this.escapeId(options.schema)}.${this.escapeId(options.tableName)}`
1693
+ : this.quoteTable(options.tableName);
1694
+ const events = (options.events || []).join(', ');
1695
+ const level = options.level === 'STATEMENT' ? '' : ' FOR EACH ROW';
1696
+ const sql = `CREATE TRIGGER ${triggerName}\n${options.timing} ${events} ON ${tableName}${level}\nBEGIN\n${options.body}\nEND`;
1697
+ await this.query(sql);
1698
+ }
1699
+ async dropTrigger(triggerName, _tableName, options) {
1700
+ const name = options?.schema
1701
+ ? `${this.escapeId(options.schema)}.${this.escapeId(triggerName)}`
1702
+ : this.escapeId(triggerName);
1703
+ if (options?.ifExists) {
1704
+ const exists = await this.hasTrigger(triggerName, _tableName, options.schema);
1705
+ if (!exists)
1706
+ return;
1707
+ }
1708
+ await this.query(`DROP TRIGGER ${name}`);
1709
+ }
1710
+ async hasTrigger(triggerName, _tableName, schema) {
1711
+ let sql = `SELECT TRIGGER_NAME FROM SYS.TRIGGERS WHERE TRIGGER_NAME = '${triggerName.toUpperCase()}'`;
1712
+ if (schema)
1713
+ sql += ` AND SCHEMA_NAME = '${schema.toUpperCase()}'`;
1714
+ const result = await this.query(sql);
1715
+ return result.rows.length > 0;
1716
+ }
1717
+ // ==================== Row-Level Security stubs (see createSecurityPolicy) ====================
1718
+ async createPolicy(_options) {
1719
+ throw new Error('HANA has no row-level-security policy statement; use Structured/Analytic Privileges instead.');
1720
+ }
1721
+ async dropPolicy(_policyName, _tableName, _options) {
1722
+ throw new Error('HANA has no row-level-security policy statement; use Structured/Analytic Privileges instead.');
1723
+ }
1724
+ async enableRLS() {
1725
+ throw new Error('HANA has no row-level-security toggle; use Structured/Analytic Privileges instead.');
1726
+ }
1727
+ async disableRLS() {
1728
+ throw new Error('HANA has no row-level-security toggle; use Structured/Analytic Privileges instead.');
1729
+ }
1730
+ async hasPolicy() {
1731
+ return false;
1732
+ }
1733
+ // ==================== Comments ====================
1734
+ async commentTable(tableName, comment) {
1735
+ await this.query(`COMMENT ON TABLE ${this.quoteTable(tableName)} IS '${comment.replace(/'/g, "''")}'`);
1736
+ }
1737
+ async commentColumn(tableName, columnName, comment) {
1738
+ await this.query(`COMMENT ON COLUMN ${this.quoteTable(tableName)}.${this.escapeId(columnName)} IS '${comment.replace(/'/g, "''")}'`);
1739
+ }
1740
+ }
1741
+ exports.HanaDialect = HanaDialect;