@nocobase/database 1.9.0-beta.4 → 1.9.0-beta.6

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.
@@ -87,7 +87,7 @@ const _SmartCursorBuilder = class _SmartCursorBuilder {
87
87
  AND a.attnum = ANY(ix.indkey)
88
88
  AND t.relkind = 'r'
89
89
  AND n.nspname = current_schema()
90
- AND t.relname = $1
90
+ AND t.relname = ?
91
91
  ORDER BY
92
92
  i.relname,
93
93
  array_position(ix.indkey, a.attnum)
@@ -145,8 +145,9 @@ const _SmartCursorBuilder = class _SmartCursorBuilder {
145
145
  isUnique: indexType < 3
146
146
  });
147
147
  }
148
- const index = indexes.get(row.INDEX_NAME);
149
- index.columns[row.SEQ_IN_INDEX - 1] = row.COLUMN_NAME;
148
+ const seqInIndex = dialect === "postgres" ? row.seq_in_index : row.SEQ_IN_INDEX;
149
+ const index = indexes.get(indexName);
150
+ index.columns[seqInIndex - 1] = columnName;
150
151
  }
151
152
  for (const index of indexes.values()) {
152
153
  if (index.isPrimary) {
package/lib/index.d.ts CHANGED
@@ -41,3 +41,5 @@ export * from './view-collection';
41
41
  export { default as fieldTypeMap } from './view/field-type-map';
42
42
  export * from './view/view-inference';
43
43
  export * from './update-guard';
44
+ export { default as operators } from './operators';
45
+ export { filterIncludes, mergeIncludes } from './utils/filter-include';
package/lib/index.js CHANGED
@@ -54,8 +54,11 @@ __export(src_exports, {
54
54
  ValidationErrorItem: () => import_sequelize.ValidationErrorItem,
55
55
  default: () => import_database.Database,
56
56
  fieldTypeMap: () => import_field_type_map.default,
57
+ filterIncludes: () => import_filter_include.filterIncludes,
57
58
  fn: () => import_sequelize.fn,
58
59
  literal: () => import_sequelize.literal,
60
+ mergeIncludes: () => import_filter_include.mergeIncludes,
61
+ operators: () => import_operators.default,
59
62
  snakeCase: () => import_utils.snakeCase,
60
63
  sqlParser: () => import_sql_parser.default,
61
64
  where: () => import_sequelize.where
@@ -96,6 +99,8 @@ __reExport(src_exports, require("./view-collection"), module.exports);
96
99
  var import_field_type_map = __toESM(require("./view/field-type-map"));
97
100
  __reExport(src_exports, require("./view/view-inference"), module.exports);
98
101
  __reExport(src_exports, require("./update-guard"), module.exports);
102
+ var import_operators = __toESM(require("./operators"));
103
+ var import_filter_include = require("./utils/filter-include");
99
104
  // Annotate the CommonJS export names for ESM import in node:
100
105
  0 && (module.exports = {
101
106
  BaseError,
@@ -114,8 +119,11 @@ __reExport(src_exports, require("./update-guard"), module.exports);
114
119
  ValidationError,
115
120
  ValidationErrorItem,
116
121
  fieldTypeMap,
122
+ filterIncludes,
117
123
  fn,
118
124
  literal,
125
+ mergeIncludes,
126
+ operators,
119
127
  snakeCase,
120
128
  sqlParser,
121
129
  where,
@@ -126,6 +126,9 @@ const _MagicAttributeModel = class _MagicAttributeModel extends import_model.Mod
126
126
  return this;
127
127
  }
128
128
  if (!options) options = {};
129
+ if (this.dataValues[this.magicAttribute] === null) {
130
+ this.dataValues[this.magicAttribute] = {};
131
+ }
129
132
  if (!options.raw) {
130
133
  originalValue = this.dataValues[key];
131
134
  }
@@ -6,8 +6,5 @@
6
6
  * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
- declare const _default: {
10
- $empty(_: any, ctx: any): any;
11
- $notEmpty(_: any, ctx: any): any;
12
- };
9
+ declare const _default: Record<string, any>;
13
10
  export default _default;
@@ -7,7 +7,6 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
  declare const _default: {
10
- $empty(_: any, ctx: any): any;
11
- $notEmpty(_: any, ctx: any): any;
10
+ [x: string]: any;
12
11
  };
13
12
  export default _default;
@@ -150,7 +150,7 @@ const _OptionsParser = class _OptionsParser {
150
150
  if (defaultSortField && !((_b = this.options) == null ? void 0 : _b.group)) {
151
151
  defaultSortField = import_lodash.default.castArray(defaultSortField);
152
152
  for (const key of defaultSortField) {
153
- if (!sort.includes(key)) {
153
+ if (!sort.includes(key) && !sort.includes(`-${key}`)) {
154
154
  sort.push(key);
155
155
  }
156
156
  }
@@ -111,7 +111,14 @@ const _PostgresQueryInterface = class _PostgresQueryInterface extends import_que
111
111
  return results[0]["exists"];
112
112
  }
113
113
  async listViews() {
114
- const sql = `
114
+ var _a;
115
+ const targetSchema = ((_a = this.db.options) == null ? void 0 : _a.schema) || "public";
116
+ const sql = targetSchema ? `
117
+ SELECT viewname as name, definition, schemaname as schema
118
+ FROM pg_views
119
+ WHERE schemaname = '${targetSchema}'
120
+ ORDER BY viewname;
121
+ ` : `
115
122
  SELECT viewname as name, definition, schemaname as schema
116
123
  FROM pg_views
117
124
  WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
@@ -154,6 +154,8 @@ export declare class Repository<TModelAttributes extends {} = any, TCreationAttr
154
154
  * return count by filter
155
155
  */
156
156
  count(countOptions?: CountOptions): Promise<number>;
157
+ getEstimatedRowCount(): Promise<number>;
158
+ private getOracleSchema;
157
159
  aggregate(options: AggregateOptions & {
158
160
  optionsTransformer?: (options: any) => any;
159
161
  }): Promise<any>;
package/lib/repository.js CHANGED
@@ -163,6 +163,83 @@ const _Repository = class _Repository {
163
163
  transaction: transaction2
164
164
  });
165
165
  }
166
+ async getEstimatedRowCount() {
167
+ var _a, _b, _c, _d;
168
+ if (import_lodash2.default.isFunction(this.collection["isSql"]) && this.collection["isSql"]()) {
169
+ return 0;
170
+ }
171
+ if (import_lodash2.default.isFunction(this.collection["isView"]) && this.collection["isView"]()) {
172
+ return 0;
173
+ }
174
+ const tableName = this.collection.tableName();
175
+ try {
176
+ if (this.database.isMySQLCompatibleDialect()) {
177
+ await this.database.sequelize.query(`ANALYZE TABLE ${this.collection.getTableNameWithSchema()}`);
178
+ const results = await this.database.sequelize.query(
179
+ `
180
+ SELECT table_rows FROM information_schema.tables
181
+ WHERE table_schema = DATABASE()
182
+ AND table_name = ?
183
+ `,
184
+ { replacements: [tableName], type: import_sequelize.QueryTypes.SELECT }
185
+ );
186
+ return Number(((_a = results == null ? void 0 : results[0]) == null ? void 0 : _a.table_rows) ?? 0);
187
+ }
188
+ if (this.database.isPostgresCompatibleDialect()) {
189
+ await this.database.sequelize.query(`ANALYZE ${this.collection.getTableNameWithSchema()}`);
190
+ const results = await this.database.sequelize.query(
191
+ `
192
+ SELECT reltuples::BIGINT AS estimate
193
+ FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
194
+ WHERE c.relname = ? AND n.nspname = current_schema();
195
+ `,
196
+ { replacements: [tableName], type: import_sequelize.QueryTypes.SELECT }
197
+ );
198
+ return Number(((_b = results == null ? void 0 : results[0]) == null ? void 0 : _b.estimate) ?? 0);
199
+ }
200
+ if (this.database.sequelize.getDialect() === "mssql") {
201
+ const results = await this.database.sequelize.query(
202
+ `
203
+ SELECT SUM(row_count) AS estimate
204
+ FROM sys.dm_db_partition_stats
205
+ WHERE object_id = OBJECT_ID(?) AND (index_id = 0 OR index_id = 1)
206
+ `,
207
+ { replacements: [tableName], type: import_sequelize.QueryTypes.SELECT }
208
+ );
209
+ return Number(((_c = results == null ? void 0 : results[0]) == null ? void 0 : _c.estimate) ?? 0);
210
+ }
211
+ if (this.database.sequelize.getDialect() === "oracle") {
212
+ const tableName2 = this.collection.name.toUpperCase();
213
+ const schemaName = (await this.getOracleSchema()).toUpperCase();
214
+ await this.database.sequelize.query(`BEGIN DBMS_STATS.GATHER_TABLE_STATS(:schema, :table); END;`, {
215
+ replacements: { schema: schemaName, table: tableName2 },
216
+ type: import_sequelize.QueryTypes.RAW
217
+ });
218
+ const results = await this.database.sequelize.query(
219
+ `
220
+ SELECT NUM_ROWS AS "estimate"
221
+ FROM ALL_TABLES
222
+ WHERE TABLE_NAME = :table AND OWNER = :schema
223
+ `,
224
+ {
225
+ replacements: { table: tableName2, schema: schemaName },
226
+ type: import_sequelize.QueryTypes.SELECT
227
+ }
228
+ );
229
+ return Number(((_d = results == null ? void 0 : results[0]) == null ? void 0 : _d.estimate) ?? 0);
230
+ }
231
+ } catch (error) {
232
+ this.database.logger.error(`Failed to get estimated row count for ${this.collection.name}:`, error);
233
+ return 0;
234
+ }
235
+ return 0;
236
+ }
237
+ async getOracleSchema() {
238
+ const [result] = await this.database.sequelize.query(`SELECT USER FROM DUAL`, {
239
+ type: import_sequelize.QueryTypes.SELECT
240
+ });
241
+ return (result == null ? void 0 : result["USER"]) ?? "";
242
+ }
166
243
  async aggregate(options) {
167
244
  var _a;
168
245
  const { method, field } = options;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ /**
10
+ * Merge a flat list of include descriptors by association (alias) key.
11
+ * - Nodes with the same association are merged into one.
12
+ * - `required` is OR-ed (true if any source is true).
13
+ * - Child includes are recursively merged with the same rules.
14
+ * - Empty `include` arrays are removed to keep the payload minimal.
15
+ *
16
+ * Notes:
17
+ * - Association keys are normalized via snake_case to ensure consistent merging.
18
+ * - This function is idempotent and order-insensitive for equivalent input sets.
19
+ *
20
+ * Usage example (input and output):
21
+ *
22
+ * const includesA = [
23
+ * { association: 'posts', required: true, include: [{ association: 'comments' }] },
24
+ * { association: 'profile' },
25
+ * ];
26
+ *
27
+ * const includesB = [
28
+ * { association: 'posts', include: [
29
+ * { association: 'comments', required: true },
30
+ * { association: 'tags' },
31
+ * ]
32
+ * },
33
+ * { association: 'roles', required: true },
34
+ * ];
35
+ *
36
+ * const merged = mergeIncludes([...includesA, ...includesB]);
37
+ *
38
+ * Result:
39
+ * [
40
+ * {
41
+ * association: 'posts',
42
+ * required: true,
43
+ * include: [
44
+ * { association: 'comments', required: true },
45
+ * { association: 'tags' },
46
+ * ],
47
+ * },
48
+ * { association: 'profile' },
49
+ * { association: 'roles', required: true },
50
+ * ]
51
+ */
52
+ export declare const mergeIncludes: (includes?: any[]) => any[];
53
+ export declare const filterIncludes: (where: any, includes: any, options: {
54
+ underscored: boolean;
55
+ }) => any[];
@@ -0,0 +1,118 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") {
21
+ for (let key of __getOwnPropNames(from))
22
+ if (!__hasOwnProp.call(to, key) && key !== except)
23
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
24
+ }
25
+ return to;
26
+ };
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+ var filter_include_exports = {};
29
+ __export(filter_include_exports, {
30
+ filterIncludes: () => filterIncludes,
31
+ mergeIncludes: () => mergeIncludes
32
+ });
33
+ module.exports = __toCommonJS(filter_include_exports);
34
+ var import_utils = require("../utils");
35
+ const collectAssociationPathsFromWhere = /* @__PURE__ */ __name((where) => {
36
+ const aliasPaths = /* @__PURE__ */ new Set();
37
+ const traverse = /* @__PURE__ */ __name((value) => {
38
+ if (value == null) return;
39
+ if (Array.isArray(value)) {
40
+ for (const item of value) traverse(item);
41
+ return;
42
+ }
43
+ if (typeof value === "object") {
44
+ for (const [rawKey, child] of Object.entries(value)) {
45
+ if (typeof rawKey === "string" && rawKey.startsWith("$") && rawKey.endsWith("$")) {
46
+ const inner = rawKey.slice(1, -1);
47
+ const segments = inner.split(".");
48
+ if (segments.length > 1) {
49
+ aliasPaths.add(segments.slice(0, -1).join("."));
50
+ }
51
+ }
52
+ traverse(child);
53
+ }
54
+ for (const sym of Object.getOwnPropertySymbols(value)) {
55
+ traverse(value[sym]);
56
+ }
57
+ }
58
+ }, "traverse");
59
+ traverse(where);
60
+ return Array.from(aliasPaths);
61
+ }, "collectAssociationPathsFromWhere");
62
+ const pruneIncludeTreeByPaths = /* @__PURE__ */ __name((includes = [], requiredPathsRaw, options) => {
63
+ const normalizedPaths = options.underscored ? requiredPathsRaw.map(
64
+ (p) => p.split(".").map((s) => (0, import_utils.snakeCase)(s)).join(".")
65
+ ) : requiredPathsRaw;
66
+ if (!(includes == null ? void 0 : includes.length) || !(normalizedPaths == null ? void 0 : normalizedPaths.length)) return [];
67
+ const pruned = [];
68
+ for (const inc of includes) {
69
+ const association = inc == null ? void 0 : inc.association;
70
+ if (!association) continue;
71
+ const assocKey = options.underscored ? (0, import_utils.snakeCase)(String(association)) : String(association);
72
+ const matched = normalizedPaths.filter((p) => p === assocKey || p.startsWith(assocKey + "."));
73
+ if (!matched.length) continue;
74
+ const childRemainders = matched.map((p) => p === assocKey ? null : p.slice(assocKey.length + 1)).filter(Boolean);
75
+ const children = pruneIncludeTreeByPaths(inc.include ?? [], childRemainders, options);
76
+ const copy = { ...inc };
77
+ if (children.length) {
78
+ copy.include = children;
79
+ } else if ("include" in copy) {
80
+ delete copy.include;
81
+ }
82
+ pruned.push(copy);
83
+ }
84
+ return pruned;
85
+ }, "pruneIncludeTreeByPaths");
86
+ const mergeIncludes = /* @__PURE__ */ __name((includes = []) => {
87
+ const byAssociation = /* @__PURE__ */ new Map();
88
+ const mergeAll = /* @__PURE__ */ __name((list = []) => {
89
+ for (const inc of list) {
90
+ const association = inc == null ? void 0 : inc.association;
91
+ if (!association) continue;
92
+ const key = (0, import_utils.snakeCase)(String(association));
93
+ if (!byAssociation.has(key)) {
94
+ byAssociation.set(key, { ...inc, include: void 0 });
95
+ }
96
+ const target = byAssociation.get(key);
97
+ if (inc.required) target.required = true;
98
+ const mergedChildren = mergeIncludes([...target.include ?? [], ...inc.include ?? []]);
99
+ if (mergedChildren.length) {
100
+ target.include = mergedChildren;
101
+ } else if ("include" in target) {
102
+ delete target.include;
103
+ }
104
+ }
105
+ }, "mergeAll");
106
+ mergeAll(includes);
107
+ return Array.from(byAssociation.values());
108
+ }, "mergeIncludes");
109
+ const filterIncludes = /* @__PURE__ */ __name((where, includes, options) => {
110
+ const path = collectAssociationPathsFromWhere(where);
111
+ const result = pruneIncludeTreeByPaths(includes, path, options);
112
+ return result;
113
+ }, "filterIncludes");
114
+ // Annotate the CommonJS export names for ESM import in node:
115
+ 0 && (module.exports = {
116
+ filterIncludes,
117
+ mergeIncludes
118
+ });
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@nocobase/database",
3
- "version": "1.9.0-beta.4",
3
+ "version": "1.9.0-beta.6",
4
4
  "description": "",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",
7
7
  "license": "AGPL-3.0",
8
8
  "dependencies": {
9
- "@nocobase/logger": "1.9.0-beta.4",
10
- "@nocobase/utils": "1.9.0-beta.4",
9
+ "@nocobase/logger": "1.9.0-beta.6",
10
+ "@nocobase/utils": "1.9.0-beta.6",
11
11
  "async-mutex": "^0.3.2",
12
12
  "chalk": "^4.1.1",
13
13
  "cron-parser": "4.4.0",
@@ -38,5 +38,5 @@
38
38
  "url": "git+https://github.com/nocobase/nocobase.git",
39
39
  "directory": "packages/database"
40
40
  },
41
- "gitHead": "0276aa12d87f82c73d62a9712134fe363e2c772a"
41
+ "gitHead": "ada5e359a91135cb9baf605c04c5c380f067a046"
42
42
  }