UNPKG

@egi/smart-db

Version:

Unified Smart DB Access

433 lines (432 loc) 21.1 kB
import { Observable } from "rxjs"; import { AbstractModel } from "./models/abstract-model"; import { SmartDbVersionViewModel } from "./models/smart-db-version-view-model"; import { SmartDbDictionary } from "./models/smart-db-dictionary"; import { SmartDb } from "./smart-db"; import { SmartLog, SmartLogOptions } from "@egi/smart-log"; import { SmartDbSqlBuildData } from "./smart-db-sql-build-data"; import { SmartError } from "./smart-error"; import { SmartDbDateTimeMode, SmartToolOptions } from "./smart-tools"; /** * Lifecycle states of a {@link SmartDb} database connection. * - `INIT` — object created, connection not yet attempted. * - `PREPARING` — driver is establishing the connection. * - `CONNECTED` — connection established; schema upgrades may still be running. * - `READY` — fully initialized and ready for queries. * - `CLOSED` — connection has been closed. * - `ERROR` — initialization or connection failed; check {@link SmartDb.lastError}. */ export declare enum SmartDbReadyState { INIT = 0, PREPARING = 1, CONNECTED = 2, READY = 3, CLOSED = 4, ERROR = 5 } export interface ModelClass<T> { attributeMap: ModelAttributeMap; new (): T; getClassName(): string; getTableName(): string; getPkSequenceName(): string; getPrimaryKey(): string; from(other: any): any; getPrimaryKeyName(): string; } export interface DbDictionaryEntry { cls: ModelClass<any>; } /** Configuration options passed to the {@link SmartDb} constructor. */ export interface SmartDbOptions<T extends SmartDb<T>> extends SmartToolOptions { /** When `true`, skip schema upgrades and only establish the connection (sets state to READY after CONNECTED). */ connectOnly?: boolean; /** When `true`, do not call {@link SmartDb.initDb} from the constructor. Call it manually later. */ delayInit?: boolean; /** When `true`, persists log entries to the `smart_db_log` database table once it exists. */ dbLogging?: boolean; /** Logging configuration (log level, format, etc.). */ logOptions?: SmartLogOptions; /** * One or more module names to initialize via the upgrade manager. * Each module must have a corresponding `${module}-init.sql` file in the SQL assets directory. */ module?: string | string[]; /** When `true`, LIKE patterns with `%` or `_` emit an explicit `ESCAPE '\\'` clause (needed for Oracle). */ needsExplicitEscape?: boolean; /** * Callback invoked once the database reaches READY state (or ERROR). * Receives the database instance and an error object if initialization failed. */ onReady?: (db: T, error?: any) => void; /** When `true`, suppress all log output (sets log level to Fatal). */ silent?: boolean; /** When `true`, skip automatic schema upgrades during {@link SmartDb.initDb}. */ skipAutoUpgrade?: boolean; /** One or more dictionary classes providing default data for lookup tables. */ smartDbDictionary?: typeof SmartDbDictionary | (typeof SmartDbDictionary)[]; /** Directory path where SQL upgrade script files are located. Defaults to the built-in assets directory. */ sqlFilesDirectory?: string; } /** Result returned by INSERT/UPDATE/DELETE statement execution. */ export interface SmartDbRunResult { /** Number of rows affected by the statement (alias for `changes` on some drivers). */ affected: number; /** Number of rows changed by the statement. */ changes: number; /** Auto-generated primary key of the last inserted row (0 for UPDATE/DELETE). */ lastId: number; } /** Metadata about a database table returned by {@link SmartDb.getTableInfo}. */ export interface SmartDbTableInfo { /** Descriptions of each column in the table. */ fields: SmartDbFieldDescription[]; /** The table name as it appears in the database. */ name: string; /** For Oracle: the sequence name used to generate PK values, if any. */ pkSequenceName?: string; } /** Metadata about a single column in a database table. */ export interface SmartDbFieldDescription { /** Column index (used by SQLite). */ cid?: string | number; /** Full database type string including precision/scale, e.g. `"VARCHAR2(100)"`. */ dbFullType: string; /** Base database type, e.g. `"VARCHAR2"`, `"INTEGER"`, `"DATE"`. */ dbType: string; /** Default value for the column, if any. */ defaultValue?: number | string | boolean; /** `true` if this column is (part of) the primary key. */ isPk?: boolean; /** Column name as it appears in the database. */ name: string; /** `true` if the column has a NOT NULL constraint. */ notNull?: boolean; /** `true` if this is a virtual/computed column. */ virtual?: boolean; } /** Describes a field-to-field or field-to-value comparison used inside a {@link SqlWhere} `expression` clause. */ export interface SmartDbSqlComparison { /** The left-hand side of the comparison: a column name or a {@link SqlFieldDescriptor}. */ compare: string | SqlFieldDescriptor; /** The comparison operator. Defaults to `EQ` (`=`) when omitted. */ operation?: SqlOperationType; /** The right-hand side: a scalar value or a {@link SqlFieldDescriptor}. */ with: SqlValueType | SqlFieldDescriptor; } /** Scalar value types accepted as SQL parameter values. */ export type SqlValueType = string | number | boolean | Date | null; /** Names of value types accepted as SQL parameter values. */ export type SqlValueTypes = "string" | "number" | "boolean" | "Date" | "any" | "clob" | "SqlValueType"; /** * Controls how column names are styled in query results. * - `Database` — original column names as returned by the database (e.g. `CREATED_AT`). * - `TypeScript` — camelCase aliases defined in the model's attribute map (e.g. `createdAt`). * - `All` — both styles are included in the result objects. */ export declare enum FieldNamingStyle { Database = 0, TypeScript = 1, All = 2 } export interface GenericModelData { [key: string]: any; } export interface AttributeInfo { alias?: string; alternative?: boolean; attribute: string; dbFullType?: string; dbType?: string; physical?: boolean; type: SqlValueTypes; typeScriptStyle?: boolean; virtual?: boolean; } export interface ModelDbOptions { dateTimeMode?: SmartDbDateTimeMode; useNativeDate?: boolean; } export interface MakeDbValueOptions extends ModelDbOptions { dbFullType?: string; } export interface ModelAttributeMap { [key: string]: AttributeInfo; } export interface AbstractModelGlobals<T extends AbstractModel<T, D>, D extends GenericModelData> { attributeMap: ModelAttributeMap; from: (other: T | D) => T; getTableName: () => string; } /** * Recursive WHERE clause builder. * * Each key in the object is interpreted as a column name whose value must match, * unless it is one of the reserved keys below: * * - `and` — sub-clause(s) joined with `AND`. * - `or` — sub-clause(s) joined with `OR`. * - `expression` — array of {@link SmartDbSqlComparison} for field-to-field comparisons. * * Plain key/value pairs generate equality conditions (or `LIKE` when the value contains `%`/`_`). * Pass a {@link SqlOperation} object as the value for other operators (GT, IN, IS NULL, etc.). * Pass an array as the value to generate an `IN (...)` condition automatically. * Pass `null` as the value to generate an `IS NULL` condition. * * @example * ```ts * // WHERE status = 'active' AND age >= 18 * const where: SqlWhere = { * status: 'active', * age: { operation: SqlOperationType.GE, value: 18 } * }; * ``` */ export interface SqlWhere { and?: SqlWhere | SqlWhere[]; expression?: SmartDbSqlComparison[]; or?: SqlWhere | SqlWhere[]; [key: string]: SqlValueType | SqlValueType[] | SqlOperation | SqlWhere | SqlWhere[] | SmartDbSqlComparison[]; } /** A plain object of `{columnName: value}` pairs used for INSERT and UPDATE values. */ export type SqlUpdateValues = GenericModelData; /** * ORDER BY specification. A single field name, optionally suffixed with `ASC` or `DESC`, * or an array of such strings for multi-column ordering. * @example `"name DESC"` or `["lastName ASC", "firstName ASC"]` */ export type SqlOrderBy = string | string[]; type SqlGroupBy = string | string[]; /** LIMIT and OFFSET values for paginating query results. */ export interface SqlLimit { /** Maximum number of rows to return. */ limit?: number; /** Number of rows to skip before returning results. */ offset?: number; } /** * Describes one section of a UNION / INTERSECT / MINUS set operation. * Used in the `union`, `intersect`, and `minus` arrays of {@link SmartDbSqlOptions}. */ export interface SectionDescription<T extends AbstractModel<T, D>, D extends GenericModelData> { /** Fields to SELECT in this section. */ fields?: string | SqlFieldDescriptor | (string | SqlFieldDescriptor)[] | null; /** GROUP BY clause for this section. */ groupBy?: SqlGroupBy; /** LIMIT/OFFSET for this section. */ limit?: SqlLimit; /** The model class or table name for this section. */ model: ModelClass<T> | string; /** WHERE clause for this section. */ where?: SqlWhere; } /** * Full set of options for {@link SmartDb.get} and related query methods. * * @typeParam T - The model class (extends {@link AbstractModel}). * @typeParam D - The plain data interface for the model. */ export interface SmartDbSqlOptions<T extends AbstractModel<T, D>, D extends GenericModelData> { /** * When `true`, a multi-column result row is collapsed into a single value * (useful when SELECTing a single aggregate column). */ collapseRow?: boolean; /** When `true`, wraps the field list in `COUNT(...)` and returns a numeric count. */ count?: boolean; /** When `true`, adds `DISTINCT` to the SELECT clause. */ distinct?: boolean; /** * Fields to SELECT. Accepts a comma-separated string, a single field name, * a {@link SqlFieldDescriptor}, or an array of field names/descriptors. * Pass `null` or omit to select all columns (`*`). */ fields?: string | SqlFieldDescriptor | (string | SqlFieldDescriptor)[] | null; /** When `true`, returns only the first result row instead of an array. */ firstOnly?: boolean; /** GROUP BY clause: a field name or array of field names. */ groupBy?: SqlGroupBy; /** HAVING clause applied after GROUP BY. Uses the same {@link SqlWhere} syntax. */ having?: SqlWhere; /** SQLite `INDEXED BY` hint to force use of a specific index. */ indexedBy?: string; /** One or more INTERSECT sections to append to the query. */ intersect?: SectionDescription<T, D> | SectionDescription<T, D>[]; /** LIMIT and OFFSET for pagination. */ limit?: SqlLimit; /** One or more MINUS (EXCEPT) sections to append to the query. */ minus?: SectionDescription<T, D> | SectionDescription<T, D>[]; /** ORDER BY clause: a field name (optionally with `ASC`/`DESC`) or array of field names. */ orderBy?: SqlOrderBy; /** Controls whether results use database column names or camelCase TypeScript aliases. */ style?: FieldNamingStyle; /** One or more UNION sections to append to the query. */ union?: SectionDescription<T, D> | SectionDescription<T, D>[]; /** WHERE clause to filter results. */ where?: SqlWhere; } /** * SQL comparison operators used in {@link SqlOperation} and {@link SmartDbSqlComparison}. * Use the helper functions from `smart-db-globals` (e.g. `OP()`, `IN()`, `LITERAL()`) * to construct {@link SqlOperation} objects rather than building them manually. */ export declare enum SqlOperationType { GT = ">", GE = ">=", LT = "<", LE = "<=", EQ = "=", NE = "!=", IN = "IN", NOT_IN = "NOT IN", LIKE = "LIKE", NOT_LIKE = "NOT LIKE", IS_NULL = "IS NULL", IS_NOT_NULL = "IS NOT NULL", LITERAL = "LITERAL", BETWEEN = "BETWEEN", VALUE = "VALUE" } /** * Aggregate/field operations used in {@link SqlFieldDescriptor} for SELECT field expressions. * Use the helper functions `COUNT()`, `SUM()`, `AVG()`, `MIN()`, `MAX()`, `FIELD()`, `VALUE()` * from `smart-db-globals` to create descriptors. */ export declare enum SqlFieldOperationType { VALUE = "VALUE", FIELD = "FIELD", COUNT = "COUNT", MIN = "MIN", MAX = "MAX", AVG = "AVG", SUM = "SUM", COUNT_FIELD = "COUNT_FIELD", COALESCE = "COALESCE" } /** * Describes a SELECT field expression such as an aggregate function or aliased column. * Construct instances using the helper functions exported from `smart-db-globals`: * `COUNT()`, `SUM()`, `AVG()`, `MIN()`, `MAX()`, `FIELD()`, `VALUE()`. */ export interface SqlFieldDescriptor { /** Optional SQL alias (`AS alias`) for the expression. */ alias?: string; /** When `true`, the `value` is emitted verbatim into SQL without quoting or translation. */ literal?: boolean; /** The aggregate or field operation to apply. */ operation: SqlFieldOperationType; /** The operand(s) for the operation: a field name, scalar value, or nested descriptor. */ value: SqlFieldDescriptor | SqlValueType | (SqlFieldDescriptor | SqlValueType)[]; } /** * A WHERE condition with an explicit operator. * Used as the value for a column key in a {@link SqlWhere} object. * Prefer the helper functions `OP()`, `IN()`, `NE()`, `LITERAL()` from `smart-db-globals`. * * @example * ```ts * // WHERE age BETWEEN 18 AND 65 * const where: SqlWhere = { age: { operation: SqlOperationType.BETWEEN, value: [18, 65] } }; * ``` */ export interface SqlOperation { /** For LITERAL operations: the operator to use before the literal value. Defaults to `EQ`. */ literalOperation?: SqlOperationType; /** The comparison operator. */ operation: SqlOperationType; /** The value(s) to compare against. Use an array for IN/NOT_IN and BETWEEN. */ value: SqlValueType | SqlValueType[]; } export interface KeyValueList { [key: string]: SqlValueType; } export interface IndexedGenericModelData<T extends AbstractModel<T, D>, D extends GenericModelData> { [key: string]: T | GenericModelData; } export interface SmartDbSqlBuildDataResults { sql: string; values: SqlValueType[]; } export interface SmartDbConnector { } /** * Public interface of {@link SmartDb}. * Describes all methods and properties available to consumers of any SmartDB driver. */ export interface SmartDbInterface { /** `true` when SQL statements are being written to the database log table. */ readonly dbLogging: boolean; /** `true` when the database connection has been established (CONNECTED or later). */ readonly isConnected: boolean; /** `true` when the database is fully initialized and ready for queries. */ readonly isReady: boolean; /** The last error that occurred during initialization or query execution. */ lastError: SmartError; /** Observable that emits every time the ready-state changes. */ readonly onReady: Observable<SmartDbReadyState>; /** Current lifecycle state of the database connection. */ readonly readyState: SmartDbReadyState; /** `true` when the driver supports synchronous calls (SQLite only). */ readonly supportSyncCalls: boolean; /** Active date/time conversion mode for this connection. */ readonly dateTimeMode: SmartDbDateTimeMode; /** Returns a string identifying the database type, e.g. `"sqlite"`, `"mysql"`, `"oracle"`. */ getDatabaseType(): string; /** Returns metadata about the columns of a database table. */ getTableInfo(table: string): Promise<SmartDbTableInfo>; /** Returns the raw underlying driver connection object. */ getDb(): any; /** Returns the current SmartDb options. */ getOptions(): SmartDbOptions<any>; /** Resolves when the database reaches `targetState` (defaults to READY). */ databaseReady(targetState?: SmartDbReadyState): Promise<SmartDbReadyState>; /** Runs schema upgrade scripts and marks the database as READY. */ initDb(): Promise<SmartDbVersionViewModel[]>; /** Closes the connection asynchronously. */ close(): Promise<boolean>; /** Closes the connection synchronously. */ closeSync(): boolean; commit(): Promise<void>; commitSync(): void; rollback(): Promise<void>; rollbackSync(): void; hasTransaction(): boolean; hasConcurrentTransactions(): boolean; get<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, options: SmartDbSqlOptions<T, D>): Promise<(T | D)[] | T | D | SqlValueType | IndexedGenericModelData<T, D> | string | string[] | false>; /** Returns the first matching model instance. Alias for `get()` with `firstOnly: true`. */ async<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, options: SmartDbSqlOptions<T, D>): Promise<T>; getFirst<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, where?: SqlWhere, fields?: SqlFieldDescriptor | string | (SqlFieldDescriptor | string)[] | null, orderBy?: SqlOrderBy): Promise<T>; getAll<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, where?: SqlWhere, fields?: string | string[] | null, orderBy?: SqlOrderBy, limit?: SqlLimit): Promise<T[]>; getAllWithOptions<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, options: SmartDbSqlOptions<T, D>): Promise<T[]>; insert<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, values: SqlUpdateValues | T): Promise<number>; update<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, values: SqlUpdateValues | T, where?: SqlWhere): Promise<number>; delete<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, where?: SqlWhere): Promise<number>; exists<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, type?: "view" | "table" | "index", indexTableName?: string): Promise<boolean>; getSync<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, options: SmartDbSqlOptions<T, D>): (T | D)[] | T | D | SqlValueType | IndexedGenericModelData<T, D> | string | string[] | false; getFirstSync<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, where?: SqlWhere, fields?: SqlFieldDescriptor | string | (SqlFieldDescriptor | string)[] | null, orderBy?: SqlOrderBy): T | false; getFirstWithOptionsSync<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, options: SmartDbSqlOptions<T, D>): T | false; getAllSync<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, where?: SqlWhere, fields?: string | string[] | null, orderBy?: SqlOrderBy, limit?: SqlLimit): T[] | false; getAllWithOptionsSync<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, options: SmartDbSqlOptions<T, D>): T[] | false; insertSync<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, values: SqlUpdateValues | T): number | false; updateSync<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, values: SqlUpdateValues | T, where?: SqlWhere): number | false; deleteSync<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, where?: SqlWhere): number | false; existsSync<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, type?: "view" | "table" | "index", indexTableName?: string): boolean; exec(script: string, params?: SqlValueType[]): Promise<void>; execSync(script: string, params?: SqlValueType[]): boolean; execScript(script: string, ignoreDropError?: boolean): Promise<void>; execScriptSync(script: string): void; query(script: string, params?: SqlValueType[]): Promise<GenericModelData[]>; querySync(script: string, params?: SqlValueType[]): GenericModelData[]; buildSelectStatement<T extends AbstractModel<T, D>, D extends GenericModelData>(modelClass: string | ModelClass<T>, options: SmartDbSqlOptions<T, D>): SmartDbSqlBuildData; getDbConnector(): string | SmartDbConnector; getDbQuote(): string; getLastBuildData<T extends AbstractModel<T, D>, D extends GenericModelData>(): SmartDbSqlBuildData; getLastError(): SmartError; getLogger(): SmartLog; getModuleVersions(): SmartDbVersionViewModel[]; makeDbValue(value: SqlValueType, options?: MakeDbValueOptions): SqlValueType; toDate(d: Date | number | string): Date | null; toDbDate(d: Date | number): string; toDbTimestamp(d: Date | number): string; } export {};