@deterministic-code/datasources-oracle 0.1.17 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,25 @@
1
+ import type { DefaultValueOptions } from "./DefaultValueOptions.js";
2
+ export declare const EMPTY_UUID = "00000000-0000-0000-0000-000000000000";
3
+ export type SpecToken = {
4
+ spec: RegExp;
5
+ specText?: (arg: string) => string;
6
+ };
7
+ export type DefaultEntry = SpecToken & {
8
+ native: (arg: string) => string;
9
+ reverse: RegExp;
10
+ };
11
+ type DefaultToken = {
12
+ token: string;
13
+ arg?: string | boolean;
14
+ };
15
+ export declare const sqlStringLiteral: (value: string | number | boolean | null | undefined) => string;
16
+ export declare class DefaultValueConverter {
17
+ static readonly spec: Record<string, Record<string, SpecToken>>;
18
+ readonly dictionary: Record<string, Record<string, DefaultEntry>>;
19
+ constructor(options: DefaultValueOptions);
20
+ static parse(specType: string, value: unknown): DefaultToken;
21
+ convert: (specType: string, text: string) => string | null;
22
+ reverseConvert: (specType: string, native: string) => string;
23
+ private static dictionaryOf;
24
+ }
25
+ export {};
@@ -0,0 +1,172 @@
1
+ export const EMPTY_UUID = "00000000-0000-0000-0000-000000000000";
2
+ const NUMERIC_TYPES = [
3
+ "integer",
4
+ "unsignedinteger",
5
+ "smallinteger",
6
+ "unsignedsmallinteger",
7
+ "biginteger",
8
+ "unsignedbiginteger",
9
+ "float",
10
+ "decimal",
11
+ ];
12
+ const NUMERIC_LITERAL_TYPES = new Set([...NUMERIC_TYPES, "reference"]);
13
+ const CALL_FORM = /^([A-Za-z]+)\('(.*)'\)$/;
14
+ const quotedReverse = /^'(.*)'$/;
15
+ const identity = (arg) => arg;
16
+ const intLit = { spec: /^-?[0-9]+$/, specText: identity };
17
+ const uintLit = { spec: /^[0-9]+$/, specText: identity };
18
+ const floatLit = { spec: /^-?[0-9]+(\.[0-9]+)?$/, specText: identity };
19
+ export const sqlStringLiteral = (value) => `'${String(value).replace(/'/g, "''")}'`;
20
+ const escapeRe = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
21
+ const exact = (text) => new RegExp(`^${escapeRe(text)}$`);
22
+ const quoted = (arg) => sqlStringLiteral(arg);
23
+ const callTokenName = (name) => (name === "uuid" ? "Uuid" : name);
24
+ const token = (specToken, native, reverse) => ({
25
+ ...specToken,
26
+ native,
27
+ reverse,
28
+ });
29
+ export class DefaultValueConverter {
30
+ static spec = {
31
+ string: { String: { spec: /^.*$/, specText: identity } },
32
+ character: { String: { spec: /^.+$/, specText: identity } },
33
+ integer: { Numeric: intLit },
34
+ unsignedinteger: { Numeric: uintLit },
35
+ smallinteger: { Numeric: intLit },
36
+ unsignedsmallinteger: { Numeric: uintLit },
37
+ biginteger: { Numeric: intLit },
38
+ unsignedbiginteger: { Numeric: uintLit },
39
+ float: { Numeric: floatLit },
40
+ decimal: { Numeric: floatLit, String: { spec: /^.*$/, specText: identity } },
41
+ boolean: { Boolean: { spec: /^(true|false)$/, specText: identity } },
42
+ datetime: {
43
+ Now: { spec: /^Now$/ },
44
+ UtcNow: { spec: /^UtcNow$/ },
45
+ DateTime: {
46
+ spec: /^DateTime\('[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(Z|[+-][0-9]{2}:[0-9]{2})'\)$/,
47
+ specText: (arg) => `DateTime('${arg}')`,
48
+ },
49
+ },
50
+ binary: {
51
+ Hex: { spec: /^Hex\('([0-9a-fA-F]{2})*'\)$/, specText: (arg) => `Hex('${arg}')` },
52
+ },
53
+ uuid: {
54
+ NewId: { spec: /^NewId$/ },
55
+ Empty: { spec: /^Empty$/ },
56
+ Uuid: {
57
+ spec: /^uuid\('[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'\)$/,
58
+ specText: (arg) => `uuid('${arg}')`,
59
+ },
60
+ },
61
+ reference: {},
62
+ };
63
+ dictionary;
64
+ constructor(options) {
65
+ this.dictionary = DefaultValueConverter.dictionaryOf(options);
66
+ }
67
+ static parse(specType, value) {
68
+ if (value === undefined || value === null)
69
+ return { token: "None" };
70
+ const v = String(value);
71
+ const call = CALL_FORM.exec(v);
72
+ if (call !== null)
73
+ return { token: callTokenName(call[1]), arg: call[2] };
74
+ if (specType === "boolean") {
75
+ return { token: "Boolean", arg: typeof value === "boolean" ? value : v === "true" };
76
+ }
77
+ if (NUMERIC_LITERAL_TYPES.has(specType))
78
+ return { token: "Numeric", arg: v };
79
+ if (specType === "datetime") {
80
+ if (v === "Now")
81
+ return { token: "Now" };
82
+ if (v === "UtcNow")
83
+ return { token: "UtcNow" };
84
+ return { token: "DateTime", arg: v };
85
+ }
86
+ if (specType === "uuid") {
87
+ if (v === "NewId")
88
+ return { token: "NewId" };
89
+ if (v === "Empty")
90
+ return { token: "Empty" };
91
+ return { token: "Uuid", arg: v };
92
+ }
93
+ if (specType === "binary")
94
+ return { token: "Hex", arg: v };
95
+ return { token: "String", arg: v };
96
+ }
97
+ convert = (specType, text) => {
98
+ const parsed = DefaultValueConverter.parse(specType, text);
99
+ if (parsed.token === "None")
100
+ return null;
101
+ const entries = this.dictionary[specType];
102
+ if (entries === undefined)
103
+ throw new Error(`Unknown spec field type: ${specType}`);
104
+ const entry = entries[parsed.token];
105
+ if (entry === undefined)
106
+ return null;
107
+ if (!entry.spec.test(text)) {
108
+ throw new Error(`default_value '${text}' is not a valid ${specType} ${parsed.token} default`);
109
+ }
110
+ const expr = entry.native(parsed.arg === undefined ? "" : String(parsed.arg));
111
+ return expr === "" ? null : expr;
112
+ };
113
+ reverseConvert = (specType, native) => {
114
+ const entries = this.dictionary[specType];
115
+ if (entries === undefined)
116
+ throw new Error(`Unknown spec field type: ${specType}`);
117
+ for (const [name, entry] of Object.entries(entries)) {
118
+ const match = entry.reverse.exec(native);
119
+ if (match === null)
120
+ continue;
121
+ const arg = match.slice(1).find((group) => group !== undefined) ?? "";
122
+ return entry.specText === undefined ? name : entry.specText(arg);
123
+ }
124
+ throw new Error(`Native default '${native}' is not a valid ${specType} default`);
125
+ };
126
+ static dictionaryOf = (opts) => {
127
+ const spec = DefaultValueConverter.spec;
128
+ const newId = opts.newId();
129
+ const now = opts.now();
130
+ const utcNow = opts.utcNow();
131
+ const numericEntries = (type) => {
132
+ const numeric = spec[type].Numeric;
133
+ return {
134
+ Numeric: token(numeric, (arg) => arg, numeric.spec),
135
+ String: token(numeric, (arg) => arg, numeric.spec),
136
+ };
137
+ };
138
+ const quote = opts.quote ?? quoted;
139
+ const quoteReverse = opts.quoteReverse ?? quotedReverse;
140
+ const quotedString = {
141
+ String: token(spec.string.String, quote, quoteReverse),
142
+ Numeric: token(spec.string.String, quote, quoteReverse),
143
+ };
144
+ const dictionary = {
145
+ string: quotedString,
146
+ character: {
147
+ String: token(spec.character.String, quote, quoteReverse),
148
+ Numeric: token(spec.character.String, quote, quoteReverse),
149
+ },
150
+ boolean: {
151
+ Boolean: token(spec.boolean.Boolean, (arg) => (arg === "true" ? opts.booleanOn : opts.booleanOff), new RegExp(`^(${escapeRe(opts.booleanOn)}|${escapeRe(opts.booleanOff)})$`)),
152
+ },
153
+ datetime: {
154
+ Now: token(spec.datetime.Now, () => now, exact(now)),
155
+ UtcNow: token(spec.datetime.UtcNow, () => utcNow, exact(utcNow)),
156
+ DateTime: token(spec.datetime.DateTime, quoted, quotedReverse),
157
+ },
158
+ binary: {
159
+ Hex: token(spec.binary.Hex, opts.hex, opts.hexReverse),
160
+ },
161
+ uuid: {
162
+ NewId: token(spec.uuid.NewId, () => newId, exact(newId)),
163
+ Empty: token(spec.uuid.Empty, () => quoted(EMPTY_UUID), exact(quoted(EMPTY_UUID))),
164
+ Uuid: token(spec.uuid.Uuid, quoted, quotedReverse),
165
+ },
166
+ reference: {},
167
+ };
168
+ for (const type of NUMERIC_TYPES)
169
+ dictionary[type] = numericEntries(type);
170
+ return dictionary;
171
+ };
172
+ }
@@ -0,0 +1,11 @@
1
+ export type DefaultValueOptions = {
2
+ newId: () => string;
3
+ now: () => string;
4
+ utcNow: () => string;
5
+ booleanOn: string;
6
+ booleanOff: string;
7
+ hex: (arg: string) => string;
8
+ hexReverse: RegExp;
9
+ quote?: (arg: string) => string;
10
+ quoteReverse?: RegExp;
11
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,7 @@
1
- import { AlterTableStatementTextRenderer, type AlterAction } from "@deterministic-code/datasources";
1
+ import { AlterTableStatementTextRenderer, type AlterAction, type Column } from "@deterministic-code/datasources";
2
2
  export declare class OracleAlterTableStatementRenderer extends AlterTableStatementTextRenderer {
3
+ protected quote(name: string): string;
4
+ protected typeName(column: Column): string;
5
+ protected columnDefault(column: Column): string;
3
6
  protected actionSql(action: AlterAction): string;
4
7
  }
@@ -1,5 +1,19 @@
1
1
  import { AlterTableStatementTextRenderer } from "@deterministic-code/datasources";
2
+ import { OracleDriver } from "./OracleDriver.js";
2
3
  export class OracleAlterTableStatementRenderer extends AlterTableStatementTextRenderer {
4
+ quote(name) {
5
+ return OracleDriver.quote(name);
6
+ }
7
+ typeName(column) {
8
+ return OracleDriver.nativeType(column);
9
+ }
10
+ columnDefault(column) {
11
+ return column.default == null
12
+ ? ""
13
+ : column.default.name == null
14
+ ? ` DEFAULT ${OracleDriver.nativeDefault(column)}`
15
+ : ` CONSTRAINT ${this.quote(column.default.name)} DEFAULT ${OracleDriver.nativeDefault(column)}`;
16
+ }
3
17
  actionSql(action) {
4
18
  return action.kind === "addColumn"
5
19
  ? `ADD (${this.column(action.column)})`
@@ -1,4 +1,7 @@
1
1
  import { CreateTableStatementTextRenderer, type Column } from "@deterministic-code/datasources";
2
2
  export declare class OracleCreateTableStatementRenderer extends CreateTableStatementTextRenderer {
3
+ protected quote(name: string): string;
4
+ protected typeName(column: Column): string;
5
+ protected columnDefault(column: Column): string;
3
6
  protected identity(column: Column): string;
4
7
  }
@@ -1,5 +1,19 @@
1
1
  import { CreateTableStatementTextRenderer } from "@deterministic-code/datasources";
2
+ import { OracleDriver } from "./OracleDriver.js";
2
3
  export class OracleCreateTableStatementRenderer extends CreateTableStatementTextRenderer {
4
+ quote(name) {
5
+ return OracleDriver.quote(name);
6
+ }
7
+ typeName(column) {
8
+ return OracleDriver.nativeType(column);
9
+ }
10
+ columnDefault(column) {
11
+ return column.default == null
12
+ ? ""
13
+ : column.default.name == null
14
+ ? ` DEFAULT ${OracleDriver.nativeDefault(column)}`
15
+ : ` CONSTRAINT ${this.quote(column.default.name)} DEFAULT ${OracleDriver.nativeDefault(column)}`;
16
+ }
3
17
  identity(column) {
4
18
  return column.identity ? " GENERATED BY DEFAULT AS IDENTITY" : "";
5
19
  }
@@ -1,11 +1,12 @@
1
1
  import { CreateTriggerStatementTextRenderer } from "@deterministic-code/datasources";
2
+ import { OracleDriver } from "./OracleDriver.js";
2
3
  export class OracleCreateTriggerStatementRenderer extends CreateTriggerStatementTextRenderer {
3
4
  render(statement) {
4
- return `CREATE OR REPLACE TRIGGER ${statement.name}
5
- BEFORE UPDATE ON ${statement.table}
5
+ return `CREATE OR REPLACE TRIGGER ${OracleDriver.quote(statement.name)}
6
+ BEFORE UPDATE ON ${OracleDriver.quote(statement.table)}
6
7
  FOR EACH ROW
7
8
  BEGIN
8
- :NEW.${statement.updatedColumn} := ${statement.utcNow};
9
+ :NEW.${OracleDriver.quote(statement.updatedColumn)} := ${OracleDriver.utcNow()};
9
10
  END;
10
11
  /`;
11
12
  }
@@ -1,11 +1,24 @@
1
- import { DefaultValueConverter, DeterministicTypeConverter, StatementTextRenderer, type Cell, type DatasourceOptions, type ICommand, type IConnection, type IDriver, type Information, type ITransaction, type IsolationLevel, type TypeConverter } from "@deterministic-code/datasources";
1
+ import { StatementTextRenderer, type Cell, type Column, type DatasourceOptions, type ICommand, type IConnection, type IDriver, type Information, type ITransaction, type IsolationLevel, type RoutineParameter, type TypeConverter } from "@deterministic-code/datasources";
2
+ import { TypeMap } from "./TypeMap.js";
3
+ import { DefaultValueConverter } from "./DefaultValueConverter.js";
2
4
  export declare class OracleDriver implements IDriver {
5
+ static readonly procedurePayloadType = "CLOB";
6
+ static procedureSpecs: (entityName: string, byGroups: string[][], casing: {
7
+ routineName: (stem: string) => string;
8
+ pluralStem: (entity: string) => string;
9
+ }) => import("./procedureSpecs.js").ProcSpec[];
10
+ static applyKeywords: (sql: string, keyword: (word: string) => string) => string;
11
+ static quote: (name: string) => string;
12
+ static nativeType: (column: Column) => string;
13
+ static nativeDefault: (column: Column) => string;
14
+ static utcNow: () => string;
15
+ static nativeParam: (parameter: RoutineParameter) => string;
3
16
  readonly statementRenderer: StatementTextRenderer;
4
17
  readonly information: Information;
5
18
  rowTypeConverters: Record<string, TypeConverter<Cell, Cell>>;
6
19
  readonly quoteLeft = "\"";
7
20
  readonly quoteRight = "\"";
8
- readonly typeConverter: DeterministicTypeConverter;
21
+ readonly typeConverter: TypeMap;
9
22
  readonly defaultValueConverter: DefaultValueConverter;
10
23
  columnType: (name: string) => string;
11
24
  connection: (options?: DatasourceOptions) => IConnection;
@@ -1,8 +1,54 @@
1
- import { columnType as identityColumnType, DefaultValueConverter, DeterministicTypeConverter, StatementTextRenderer, StubTransaction, } from "@deterministic-code/datasources";
1
+ import { columnType as identityColumnType, StatementTextRenderer, StubTransaction, } from "@deterministic-code/datasources";
2
2
  import { OracleConnection } from "./connection.js";
3
3
  import { OracleStatementRenderer } from "./OracleStatementRenderer.js";
4
+ import { procedureSpecs } from "./procedureSpecs.js";
5
+ import { applyKeywords } from "./applyKeywords.js";
6
+ import { TypeMap } from "./TypeMap.js";
7
+ import { DefaultValueConverter } from "./DefaultValueConverter.js";
4
8
  const statements = new StatementTextRenderer(t => new OracleStatementRenderer(t));
9
+ const types = new TypeMap({
10
+ string: "CLOB",
11
+ character: "CHAR(1)",
12
+ integer: "NUMBER(10)",
13
+ unsignedinteger: "NUMBER(10)",
14
+ smallinteger: "NUMBER(5)",
15
+ unsignedsmallinteger: "NUMBER(5)",
16
+ biginteger: "NUMBER(19)",
17
+ unsignedbiginteger: "NUMBER(20)",
18
+ float: "BINARY_DOUBLE",
19
+ decimal: "NUMBER",
20
+ boolean: "NUMBER(1)",
21
+ datetime: "TIMESTAMP",
22
+ binary: "BLOB",
23
+ uuid: "VARCHAR2(36)",
24
+ reference: "NUMBER(10)",
25
+ });
26
+ const defaults = new DefaultValueConverter({
27
+ newId: () => "SYS_GUID()",
28
+ now: () => "LOCALTIMESTAMP",
29
+ utcNow: () => "SYS_EXTRACT_UTC(SYSTIMESTAMP)",
30
+ booleanOn: "1",
31
+ booleanOff: "0",
32
+ hex: (arg) => `HEXTORAW('${arg}')`,
33
+ hexReverse: /^HEXTORAW\('(.*)'\)$/,
34
+ });
5
35
  export class OracleDriver {
36
+ static procedurePayloadType = "CLOB";
37
+ static procedureSpecs = procedureSpecs;
38
+ static applyKeywords = applyKeywords;
39
+ static quote = (name) => `"${name}"`;
40
+ static nativeType = (column) => column.field ? types.convertField(column.field) : column.type;
41
+ static nativeDefault = (column) => column.default == null
42
+ ? ""
43
+ : column.field === undefined
44
+ ? column.default.value
45
+ : defaults.convert(column.field.type, column.default.value) ?? column.default.value;
46
+ static utcNow = () => defaults.convert("datetime", "UtcNow") ?? "";
47
+ static nativeParam = (parameter) => parameter.type === "payload"
48
+ ? OracleDriver.procedurePayloadType
49
+ : parameter.field
50
+ ? types.convertField(parameter.field)
51
+ : types.dictionary[parameter.type] ?? parameter.type;
6
52
  statementRenderer = statements;
7
53
  information = {
8
54
  descriptions: ["Oracle driver."],
@@ -20,41 +66,8 @@ export class OracleDriver {
20
66
  rowTypeConverters = {};
21
67
  quoteLeft = '"';
22
68
  quoteRight = '"';
23
- typeConverter = new DeterministicTypeConverter({
24
- string: "CLOB",
25
- character: "CHAR(1)",
26
- number: "NUMBER(10)",
27
- integer: "NUMBER(10)",
28
- unsignedinteger: "NUMBER(10)",
29
- smallinteger: "NUMBER(5)",
30
- unsignedsmallinteger: "NUMBER(5)",
31
- biginteger: "NUMBER(19)",
32
- unsignedbiginteger: "NUMBER(20)",
33
- float: "BINARY_DOUBLE",
34
- decimal: "NUMBER",
35
- boolean: "NUMBER(1)",
36
- datetime: "TIMESTAMP",
37
- binary: "BLOB",
38
- uuid: "VARCHAR2(36)",
39
- reference: "NUMBER(10)",
40
- }, {
41
- dialectName: "oracle",
42
- stringUnlimited: "CLOB",
43
- stringSized: (n) => `VARCHAR2(${n})`,
44
- character: "CHAR",
45
- decimal: { kind: "required", type: "NUMBER" },
46
- binaryUnlimited: "BLOB",
47
- binarySized: (n) => `RAW(${n})`,
48
- });
49
- defaultValueConverter = new DefaultValueConverter({
50
- newId: () => "SYS_GUID()",
51
- now: () => "LOCALTIMESTAMP",
52
- utcNow: () => "SYS_EXTRACT_UTC(SYSTIMESTAMP)",
53
- booleanOn: "1",
54
- booleanOff: "0",
55
- hex: (arg) => `HEXTORAW('${arg}')`,
56
- hexReverse: /^HEXTORAW\('(.*)'\)$/,
57
- });
69
+ typeConverter = types;
70
+ defaultValueConverter = defaults;
58
71
  columnType = identityColumnType;
59
72
  connection = (options) => OracleConnection.from(options);
60
73
  command = (connection) => connection.createCommand();
@@ -1,6 +1,7 @@
1
1
  import { DropIndexStatementTextRenderer } from "@deterministic-code/datasources";
2
+ import { OracleDriver } from "./OracleDriver.js";
2
3
  export class OracleDropIndexStatementRenderer extends DropIndexStatementTextRenderer {
3
4
  render(statement) {
4
- return `DROP INDEX ${statement.name}`;
5
+ return `DROP INDEX ${OracleDriver.quote(statement.name)}`;
5
6
  }
6
7
  }
@@ -1,7 +1,8 @@
1
1
  import { DropTableStatementTextRenderer } from "@deterministic-code/datasources";
2
+ import { OracleDriver } from "./OracleDriver.js";
2
3
  export class OracleDropTableStatementRenderer extends DropTableStatementTextRenderer {
3
4
  render(statement) {
4
- return `BEGIN EXECUTE IMMEDIATE 'DROP TABLE ${statement.table} CASCADE CONSTRAINTS'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END;
5
+ return `BEGIN EXECUTE IMMEDIATE 'DROP TABLE ${OracleDriver.quote(statement.table)} CASCADE CONSTRAINTS'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END;
5
6
  /`;
6
7
  }
7
8
  }
@@ -1,6 +1,7 @@
1
1
  import { DropTriggerStatementTextRenderer } from "@deterministic-code/datasources";
2
+ import { OracleDriver } from "./OracleDriver.js";
2
3
  export class OracleDropTriggerStatementRenderer extends DropTriggerStatementTextRenderer {
3
4
  render(statement) {
4
- return `DROP TRIGGER ${statement.name}`;
5
+ return `DROP TRIGGER ${OracleDriver.quote(statement.name)}`;
5
6
  }
6
7
  }
@@ -0,0 +1,7 @@
1
+ import { CreateIndexStatementTextRenderer, type ForeignKeyStatement } from "@deterministic-code/datasources";
2
+ export declare class OracleCreateIndexStatementRenderer extends CreateIndexStatementTextRenderer {
3
+ protected quote(name: string): string;
4
+ }
5
+ export declare class OracleForeignKeyStatementRenderer {
6
+ render(statement: ForeignKeyStatement): string;
7
+ }
@@ -0,0 +1,18 @@
1
+ import { CreateIndexStatementTextRenderer, } from "@deterministic-code/datasources";
2
+ import { OracleDriver } from "./OracleDriver.js";
3
+ export class OracleCreateIndexStatementRenderer extends CreateIndexStatementTextRenderer {
4
+ quote(name) {
5
+ return OracleDriver.quote(name);
6
+ }
7
+ }
8
+ export class OracleForeignKeyStatementRenderer {
9
+ render(statement) {
10
+ const table = OracleDriver.quote(statement.table);
11
+ const name = OracleDriver.quote(statement.name);
12
+ if (statement.kind === "drop")
13
+ return `ALTER TABLE ${table} DROP CONSTRAINT ${name}`;
14
+ const cols = statement.columns.map(OracleDriver.quote).join(", ");
15
+ const refs = statement.referencesColumns.map(OracleDriver.quote).join(", ");
16
+ return `ALTER TABLE ${table} ADD CONSTRAINT ${name} FOREIGN KEY (${cols}) REFERENCES ${OracleDriver.quote(statement.referencesTable)} (${refs})`;
17
+ }
18
+ }
@@ -0,0 +1,4 @@
1
+ import type { SqlSeedStatement } from "@deterministic-code/datasources";
2
+ export declare class OracleSqlSeedStatementRenderer {
3
+ render(statement: SqlSeedStatement): string;
4
+ }
@@ -0,0 +1,49 @@
1
+ const quote = (ident) => `"${ident}"`;
2
+ const quoteString = (value) => `'${String(value).replace(/'/g, "''")}'`;
3
+ const truthy = (value) => value === true || value === "true" || value === "True" || value === 1;
4
+ const numeric = new Set([
5
+ "integer",
6
+ "unsignedinteger",
7
+ "smallinteger",
8
+ "unsignedsmallinteger",
9
+ "biginteger",
10
+ "unsignedbiginteger",
11
+ "reference",
12
+ "float",
13
+ "decimal",
14
+ ]);
15
+ const defaultSql = (type, def) => {
16
+ const text = String(def);
17
+ if (type === "datetime" && text === "UtcNow")
18
+ return "SYS_EXTRACT_UTC(SYSTIMESTAMP)";
19
+ if (type === "datetime" && text === "Now")
20
+ return "LOCALTIMESTAMP";
21
+ if (type === "uuid" && text === "NewId")
22
+ return "SYS_GUID()";
23
+ if (type === "boolean")
24
+ return text === "true" || text === "True" ? "1" : "0";
25
+ return quoteString(text);
26
+ };
27
+ const literal = (column) => {
28
+ if (column.value == null)
29
+ return column.field.defaultValue != null ? defaultSql(column.field.type, column.field.defaultValue) : "NULL";
30
+ if (column.field.type === "boolean")
31
+ return truthy(column.value) ? "1" : "0";
32
+ if (numeric.has(column.field.type))
33
+ return String(column.value);
34
+ if (column.field.type === "binary")
35
+ return `HEXTORAW('${column.value}')`;
36
+ return quoteString(column.value);
37
+ };
38
+ export class OracleSqlSeedStatementRenderer {
39
+ render(statement) {
40
+ const table = quote(statement.table);
41
+ if (statement.kind === "delete") {
42
+ const key = statement.columns[0];
43
+ return `DELETE FROM ${table} WHERE ${quote(key.column)} = ${literal(key)}`;
44
+ }
45
+ const cols = statement.columns.map((c) => quote(c.column)).join(", ");
46
+ const values = statement.columns.map(literal).join(", ");
47
+ return `INSERT INTO ${table} (${cols}) VALUES (${values})`;
48
+ }
49
+ }
@@ -1,4 +1,4 @@
1
- import { AlterTableStatement, CreateBulkCreateStoredProcedureStatement, CreateBulkDeleteStoredProcedureStatement, CreateBulkUpdateStoredProcedureStatement, CreateCreateStoredProcedureStatement, CreateDeleteStoredProcedureStatement, CreateNestedReadStoredProcedureStatement, CreateReadStoredProcedureStatement, CreateTableStatement, CreateTriggerStatement, CreateUpdateStoredProcedureStatement, DropIndexStatement, DropTableStatement, DropTriggerStatement, StatementRenderer, type StatementTextRenderer } from "@deterministic-code/datasources";
1
+ import { AlterTableStatement, CreateBulkCreateStoredProcedureStatement, CreateBulkDeleteStoredProcedureStatement, CreateBulkUpdateStoredProcedureStatement, CreateCreateStoredProcedureStatement, CreateDeleteStoredProcedureStatement, CreateNestedReadStoredProcedureStatement, CreateReadStoredProcedureStatement, CreateIndexStatement, CreateTableStatement, CreateTriggerStatement, CreateUpdateStoredProcedureStatement, DropIndexStatement, DropTableStatement, DropTriggerStatement, ForeignKeyStatement, SqlSeedStatement, StatementRenderer, type StatementTextRenderer } from "@deterministic-code/datasources";
2
2
  export declare class OracleStatementRenderer extends StatementRenderer {
3
3
  constructor(text: StatementTextRenderer);
4
4
  protected visitCreateTable(create: CreateTableStatement): string;
@@ -15,4 +15,7 @@ export declare class OracleStatementRenderer extends StatementRenderer {
15
15
  protected visitCreateBulkCreateStoredProcedureStatement(statement: CreateBulkCreateStoredProcedureStatement): string;
16
16
  protected visitCreateBulkUpdateStoredProcedureStatement(statement: CreateBulkUpdateStoredProcedureStatement): string;
17
17
  protected visitCreateBulkDeleteStoredProcedureStatement(statement: CreateBulkDeleteStoredProcedureStatement): string;
18
+ protected visitSqlSeed(seed: SqlSeedStatement): string;
19
+ protected visitCreateIndex(index: CreateIndexStatement): string;
20
+ protected visitForeignKey(key: ForeignKeyStatement): string;
18
21
  }
@@ -6,6 +6,8 @@ import { OracleDropIndexStatementRenderer } from "./OracleDropIndexStatementRend
6
6
  import { OracleDropTableStatementRenderer } from "./OracleDropTableStatementRenderer.js";
7
7
  import { OracleDropTriggerStatementRenderer } from "./OracleDropTriggerStatementRenderer.js";
8
8
  import { OracleStoredProcedureStatementRenderer } from "./OracleStoredProcedureStatementRenderer.js";
9
+ import { OracleSqlSeedStatementRenderer } from "./OracleSqlSeedStatementRenderer.js";
10
+ import { OracleCreateIndexStatementRenderer, OracleForeignKeyStatementRenderer } from "./OracleQuotedRenderers.js";
9
11
  export class OracleStatementRenderer extends StatementRenderer {
10
12
  constructor(text) {
11
13
  super(text);
@@ -52,4 +54,13 @@ export class OracleStatementRenderer extends StatementRenderer {
52
54
  visitCreateBulkDeleteStoredProcedureStatement(statement) {
53
55
  return new OracleStoredProcedureStatementRenderer().renderBulkDelete(statement);
54
56
  }
57
+ visitSqlSeed(seed) {
58
+ return new OracleSqlSeedStatementRenderer().render(seed);
59
+ }
60
+ visitCreateIndex(index) {
61
+ return new OracleCreateIndexStatementRenderer().render(index);
62
+ }
63
+ visitForeignKey(key) {
64
+ return new OracleForeignKeyStatementRenderer().render(key);
65
+ }
55
66
  }
@@ -1,6 +1,8 @@
1
1
  import { DefaultStatementTextRenderer } from "@deterministic-code/datasources";
2
- const params = (list) => list.map((p) => ` ${p.name} IN ${p.type}`).join(",\n");
3
- const jsonColumns = (columns) => columns.map((c) => `${c.name} ${c.type} PATH '$.${c.name}'`).join(", ");
2
+ import { OracleDriver } from "./OracleDriver.js";
3
+ const typeOf = (parameter) => OracleDriver.nativeParam(parameter);
4
+ const params = (list) => list.map((p) => ` ${p.name} IN ${typeOf(p)}`).join(",\n");
5
+ const jsonColumns = (columns) => columns.map((c) => `${c.name} ${typeOf(c)} PATH '$.${c.name}'`).join(", ");
4
6
  export class OracleStoredProcedureStatementRenderer {
5
7
  renderCreate(statement) {
6
8
  return `CREATE OR REPLACE PROCEDURE ${statement.name}(
@@ -14,7 +16,7 @@ END;`;
14
16
  renderNestedRead(statement) {
15
17
  const select = DefaultStatementTextRenderer.instance.render(statement.select);
16
18
  const signature = statement.arguments
17
- .map((argument) => `${argument.parameter.name} IN ${argument.parameter.type}`)
19
+ .map((argument) => `${argument.parameter.name} IN ${typeOf(argument.parameter)}`)
18
20
  .join(", ");
19
21
  return `CREATE OR REPLACE PROCEDURE ${statement.name}(${signature}) AS
20
22
  BEGIN
@@ -30,7 +32,7 @@ BEGIN
30
32
  END;`;
31
33
  }
32
34
  const signature = statement.byProperties
33
- .map((c) => `${c} IN ${statement.parameters.find((p) => p.name === c)?.type ?? statement.keyType}`)
35
+ .map((c) => `${c} IN ${typeOf(statement.parameters.find((p) => p.name === c) ?? { name: c, type: statement.keyType })}`)
34
36
  .join(", ");
35
37
  const pred = statement.byProperties.map((c) => `t.${c} = ${c}`).join(" AND ");
36
38
  return `CREATE OR REPLACE PROCEDURE ${statement.name}(${signature}) AS
@@ -60,7 +62,7 @@ END;`;
60
62
  renderDelete(statement) {
61
63
  const by = statement.byProperties.length > 0 ? statement.byProperties : [statement.key];
62
64
  const signature = by
63
- .map((c) => `${c} IN ${statement.parameters.find((p) => p.name === c)?.type ?? statement.keyType}`)
65
+ .map((c) => `${c} IN ${typeOf(statement.parameters.find((p) => p.name === c) ?? { name: c, type: statement.keyType })}`)
64
66
  .join(", ");
65
67
  const pred = by.map((c) => `t.${c} = ${c}`).join(" AND ");
66
68
  return `CREATE OR REPLACE PROCEDURE ${statement.name}(${signature}) AS
@@ -96,7 +98,7 @@ END;`;
96
98
  BEGIN
97
99
  DELETE FROM ${statement.table} t
98
100
  WHERE t.${statement.key} IN (
99
- SELECT ${statement.key} FROM JSON_TABLE(${statement.payload.name}, '$[*]' COLUMNS (${statement.key} ${statement.keyType} PATH '$.${statement.key}'))
101
+ SELECT ${statement.key} FROM JSON_TABLE(${statement.payload.name}, '$[*]' COLUMNS (${statement.key} ${OracleDriver.nativeParam({ name: statement.key, type: statement.keyType })} PATH '$.${statement.key}'))
100
102
  );
101
103
  END;`;
102
104
  }
@@ -0,0 +1,9 @@
1
+ import type { IMapping, SpecField } from "@deterministic-code/datasources";
2
+ export declare class TypeMap implements IMapping<string, string> {
3
+ readonly dictionary: Record<string, string>;
4
+ private readonly reverse;
5
+ constructor(dictionary: Record<string, string>);
6
+ convert: (from: string) => string;
7
+ convertField: (field: SpecField) => string;
8
+ reverseConvert: (native: string) => string;
9
+ }
@@ -0,0 +1,35 @@
1
+ export class TypeMap {
2
+ dictionary;
3
+ reverse = new Map();
4
+ constructor(dictionary) {
5
+ this.dictionary = dictionary;
6
+ for (const [spec, native] of Object.entries(dictionary)) {
7
+ if (!this.reverse.has(native))
8
+ this.reverse.set(native, spec);
9
+ }
10
+ }
11
+ convert = (from) => this.dictionary[from] ??
12
+ (() => {
13
+ throw new Error(`Unknown spec field type: ${from}`);
14
+ })();
15
+ convertField = (field) => {
16
+ if (field.type === "string")
17
+ return field.size == null || field.size === "unlimited" ? "CLOB" : `VARCHAR2(${field.size})`;
18
+ if (field.type === "character")
19
+ return `CHAR(${field.size == null ? 1 : Number(field.size)})`;
20
+ if (field.type === "decimal") {
21
+ if (field.size == null)
22
+ return "NUMBER";
23
+ if (Array.isArray(field.size) && field.size.length === 2)
24
+ return `NUMBER(${field.size[0]}, ${field.size[1]})`;
25
+ throw new Error(`decimal field "${field.name}" size must be [precision, scale] tuple; got ${field.size}`);
26
+ }
27
+ if (field.type === "binary")
28
+ return field.size == null || field.size === "unlimited" ? "BLOB" : `RAW(${field.size})`;
29
+ return this.convert(field.type);
30
+ };
31
+ reverseConvert = (native) => this.reverse.get(native) ??
32
+ (() => {
33
+ throw new Error(`Unknown native field type: ${native}`);
34
+ })();
35
+ }
@@ -0,0 +1 @@
1
+ export declare const applyKeywords: (sql: string, keyword: (word: string) => string) => string;
@@ -0,0 +1,14 @@
1
+ const KEYWORDS = new Set([
2
+ "AFTER", "ALTER", "AND", "AS", "AT", "BEFORE", "BEGIN", "BETWEEN", "BINARY_DOUBLE", "BLOB",
3
+ "BY", "CASCADE", "CAST", "CHAR", "CLOB", "CONSTRAINT", "CONSTRAINTS", "CREATE", "DECIMAL",
4
+ "DECLARE", "DEFAULT", "DELETE", "DROP", "EACH", "ELSE", "END", "EXCEPTION", "EXECUTE",
5
+ "EXISTS", "FALSE", "FLOAT", "FOR", "FOREIGN", "FROM", "FUNCTION", "IF", "IMMEDIATE", "IN",
6
+ "INDEX", "INNER", "INSERT", "INT", "INTEGER", "INTO", "JOIN", "KEY", "LANGUAGE",
7
+ "LOCALTIMESTAMP", "NCHAR", "NOT", "NULL", "NUMBER", "NUMERIC", "OLD", "ON", "OR", "ORDER",
8
+ "OTHERS", "PRECISION", "PRIMARY", "PROCEDURE", "RAISE", "RAW", "REFERENCES", "REPLACE",
9
+ "RETURN", "RETURNING", "ROW", "SELECT", "SET", "SMALLINT", "SQLCODE", "SYS_EXTRACT_UTC",
10
+ "SYS_GUID", "SYSTIMESTAMP", "TABLE", "THEN", "TIMESTAMP", "TRIGGER", "TRUE", "UNIQUE",
11
+ "UPDATE", "VALUES", "VARCHAR", "VARCHAR2", "WHEN", "WHERE", "ZONE",
12
+ ]);
13
+ const TOKEN = /('(?:''|[^'])*')|("(?:""|[^"])*")|(--[^\n]*)|(\/\*[\s\S]*?\*\/)|(?<word>[A-Za-z_][A-Za-z0-9_]*)/g;
14
+ export const applyKeywords = (sql, keyword) => sql.replace(TOKEN, (match, _s, _d, _lc, _bc, word) => word !== undefined && KEYWORDS.has(word.toUpperCase()) ? keyword(word) : match);
@@ -0,0 +1,10 @@
1
+ export type ProcOp = "create" | "read" | "update" | "delete" | "bulkCreate" | "bulkUpdate" | "bulkDelete";
2
+ export type ProcSpec = {
3
+ op: ProcOp;
4
+ name: string;
5
+ byProperties: string[];
6
+ };
7
+ export declare const procedureSpecs: (entityName: string, byGroups: string[][], casing: {
8
+ routineName: (stem: string) => string;
9
+ pluralStem: (entity: string) => string;
10
+ }) => ProcSpec[];
@@ -0,0 +1,17 @@
1
+ export const procedureSpecs = (entityName, byGroups, casing) => {
2
+ const name = (stem) => casing.routineName(stem);
3
+ const plural = casing.pluralStem(entityName);
4
+ const bySuffix = (props) => props.length === 0 ? "" : `_by_${props.join("_")}`;
5
+ return [
6
+ { op: "create", name: name(`create_${plural}`), byProperties: [] },
7
+ { op: "read", name: name(`read_${plural}`), byProperties: [] },
8
+ ...byGroups.flatMap((props) => [
9
+ { op: "read", name: name(`read_${plural}${bySuffix(props)}`), byProperties: props },
10
+ { op: "update", name: name(`update_${plural}${bySuffix(props)}`), byProperties: props },
11
+ { op: "delete", name: name(`delete_${plural}${bySuffix(props)}`), byProperties: props },
12
+ ]),
13
+ { op: "bulkCreate", name: name(`bulk_create_${plural}`), byProperties: [] },
14
+ { op: "bulkUpdate", name: name(`bulk_update_${plural}`), byProperties: [] },
15
+ { op: "bulkDelete", name: name(`bulk_delete_${plural}`), byProperties: [] },
16
+ ];
17
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deterministic-code/datasources-oracle",
3
- "version": "0.1.17",
3
+ "version": "0.1.29",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.js",
@@ -18,6 +18,6 @@
18
18
  "access": "public"
19
19
  },
20
20
  "dependencies": {
21
- "@deterministic-code/datasources": "^0.1.17"
21
+ "@deterministic-code/datasources": "^0.1.29"
22
22
  }
23
23
  }