-
Notifications
You must be signed in to change notification settings - Fork 21
fix: fix filter by autogenerated _id for mongodb #188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next
Are you sure you want to change the base?
Changes from all commits
4118cce
5286814
08154a9
5ba0c0e
7a7e08e
be497f3
80a250f
429a853
e4181ee
77e28e8
a05ea7a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,39 @@ | ||
| import dayjs from 'dayjs'; | ||
| import { MongoClient } from 'mongodb'; | ||
| import { Decimal128, Double } from 'bson'; | ||
| import { IAdminForthDataSourceConnector, IAdminForthSingleFilter, IAdminForthAndOrFilter, AdminForthResource } from '../types/Back.js'; | ||
| import { MongoClient, BSON, ObjectId, Decimal128, Double, UUID } from 'mongodb'; | ||
| import { IAdminForthDataSourceConnector, IAdminForthSingleFilter, IAdminForthAndOrFilter, AdminForthResource, Filters } from '../types/Back.js'; | ||
| import AdminForthBaseConnector from './baseConnector.js'; | ||
|
|
||
| import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections, } from '../types/Common.js'; | ||
|
|
||
| const UUID36 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; | ||
| const HEX24 = /^[0-9a-f]{24}$/i; // 24-hex (Mongo ObjectId) | ||
|
|
||
| function idToString(v: any) { | ||
| if (v == null) return null; | ||
| if (typeof v === "string" || typeof v === "number" || typeof v === "bigint") return String(v); | ||
|
|
||
| const s = BSON.EJSON.serialize(v); | ||
| if (s && typeof s === "object") { | ||
| if ("$oid" in s) { | ||
| return String(s.$oid); | ||
| } | ||
| if ("$uuid" in s) { | ||
| return String(s.$uuid); | ||
| } | ||
| } | ||
NoOne7135 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return String(v); | ||
| } | ||
|
|
||
| const extractSimplePkEq = (f: any, pk: string): string | null => { | ||
| while (f?.subFilters?.length === 1) f = f.subFilters[0]; | ||
| return (f?.operator === AdminForthFilterOperators.EQ && f?.field === pk && f.value != null && typeof f.value !== "object") | ||
| ? String(f.value) | ||
| : null; | ||
| }; | ||
|
|
||
| const escapeRegex = (value) => { | ||
| return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // Escapes special characters | ||
| }; | ||
|
|
||
| function normalizeMongoValue(v: any) { | ||
| if (v == null) { | ||
| return v; | ||
|
|
@@ -29,7 +54,13 @@ function normalizeMongoValue(v: any) { | |
| } | ||
|
|
||
| class MongoConnector extends AdminForthBaseConnector implements IAdminForthDataSourceConnector { | ||
|
|
||
| private pkCandidates(pkValue: any): any[] { | ||
| if (pkValue == null || typeof pkValue !== "string") return [pkValue]; | ||
| const candidates: any[] = [pkValue]; | ||
| try { candidates.push(new UUID(pkValue)); } catch(err) { console.error(`Failed to create UUID from ${pkValue}: ${err.message}`); } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @NoOne7135 console.error? so if user uses own string ids like "asdfa1231" we will say him "Error"? |
||
| try { candidates.push(new ObjectId(pkValue)); } catch(err) { console.error(`Failed to create ObjectId from ${pkValue}: ${err.message}`); } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @NoOne7135 are you sure logging is needed in both catch blocks? I suppose if there is uuid it might be not object id right? |
||
| return candidates; | ||
| } | ||
| async setupClient(url): Promise<void> { | ||
| this.client = new MongoClient(url); | ||
| (async () => { | ||
|
|
@@ -183,72 +214,45 @@ class MongoConnector extends AdminForthBaseConnector implements IAdminForthDataS | |
| }, {}); | ||
| } | ||
|
|
||
| getPrimaryKey(resource) { | ||
| for (const col of resource.dataSourceColumns) { | ||
| if (col.primaryKey) { | ||
| return col.name; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| getFieldValue(field, value) { | ||
| if (field.type == AdminForthDataTypes.DATETIME) { | ||
| if (!value) { | ||
| return null; | ||
| } | ||
| return dayjs(Date.parse(value)).toISOString(); | ||
|
|
||
| } else if (field.type == AdminForthDataTypes.DATE) { | ||
| if (!value) { | ||
| return null; | ||
| } | ||
| return dayjs(Date.parse(value)).toISOString().split('T')[0]; | ||
|
|
||
| } else if (field.type == AdminForthDataTypes.BOOLEAN) { | ||
| return value === null ? null : !!value; | ||
| } else if (field.type == AdminForthDataTypes.DECIMAL) { | ||
| if (value === null || value === undefined) { | ||
| return null; | ||
| } | ||
| return value?.toString(); | ||
| if (field.type === AdminForthDataTypes.DATETIME) { | ||
| return value ? dayjs(Date.parse(value)).toISOString() : null; | ||
| } | ||
| if (field.type === AdminForthDataTypes.DATE) { | ||
| return value ? dayjs(Date.parse(value)).toISOString().split("T")[0] : null; | ||
| } | ||
| if (field.type === AdminForthDataTypes.BOOLEAN) { | ||
| return value === null ? null : !!value; | ||
| } | ||
| if (field.type === AdminForthDataTypes.DECIMAL) { | ||
| return value === null || value === undefined ? null : value.toString(); | ||
| } | ||
| if (field.name === '_id') { | ||
| return idToString(value); | ||
| } | ||
|
|
||
| return value; | ||
| } | ||
|
|
||
|
|
||
| setFieldValue(field, value) { | ||
| if (value === undefined) return undefined; | ||
| if (value === null) return null; | ||
|
|
||
| if (value === undefined) { | ||
| return undefined; | ||
| } | ||
| if (value === null || value === '') { | ||
| return null; | ||
| } | ||
| if (field.type === AdminForthDataTypes.DATETIME) { | ||
| if (value === "" || value === null) { | ||
| return null; | ||
| } | ||
| return dayjs(value).isValid() ? dayjs(value).toDate() : null; | ||
| } | ||
|
|
||
| if (field.type === AdminForthDataTypes.INTEGER) { | ||
| if (value === "" || value === null) { | ||
| return null; | ||
| } | ||
| return Number.isFinite(value) ? Math.trunc(value) : null; | ||
| } | ||
|
|
||
| if (field.type === AdminForthDataTypes.FLOAT) { | ||
| if (value === "" || value === null) { | ||
| return null; | ||
| } | ||
| return Number.isFinite(value) ? value : null; | ||
| } | ||
|
|
||
| if (field.type === AdminForthDataTypes.DECIMAL) { | ||
| if (value === "" || value === null) { | ||
| return null; | ||
| } | ||
| return value.toString(); | ||
| } | ||
|
|
||
| return value; | ||
| } | ||
|
|
||
|
|
@@ -299,34 +303,54 @@ class MongoConnector extends AdminForthBaseConnector implements IAdminForthDataS | |
| .map((f) => this.getFilterQuery(resource, f))); | ||
| } | ||
|
|
||
| async getDataWithOriginalTypes({ resource, limit, offset, sort, filters }: | ||
| { | ||
| resource: AdminForthResource, | ||
| limit: number, | ||
| offset: number, | ||
| sort: { field: string, direction: AdminForthSortDirections }[], | ||
| async getDataWithOriginalTypes({ resource, limit, offset, sort, filters }: | ||
| { | ||
| resource: AdminForthResource, | ||
| limit: number, | ||
| offset: number, | ||
| sort: { field: string, direction: AdminForthSortDirections }[], | ||
| filters: IAdminForthAndOrFilter, | ||
| } | ||
| ): Promise<any[]> { | ||
|
|
||
| // const columns = resource.dataSourceColumns.filter(c=> !c.virtual).map((col) => col.name).join(', '); | ||
| const tableName = resource.table; | ||
| const collection = this.client.db().collection(tableName); | ||
|
|
||
|
|
||
| const collection = this.client.db().collection(tableName); | ||
| const pk = this.getPrimaryKey(resource); | ||
| const pkValue = extractSimplePkEq(filters, pk); | ||
|
|
||
| if (pkValue !== null) { | ||
| let res = await collection.find({ [pk]: pkValue }).limit(1).toArray(); | ||
| if (res.length) { | ||
| return res; | ||
| } | ||
| if (UUID36.test(pkValue)) { | ||
| res = await collection.find({ [pk]: new UUID(pkValue) }).limit(1).toArray(); | ||
| } | ||
| if (res.length) { | ||
| return res; | ||
| } | ||
| if (HEX24.test(pkValue)) { | ||
| res = await collection.find({ [pk]: new ObjectId(pkValue) }).limit(1).toArray(); | ||
| } | ||
| if (res.length) { | ||
| return res; | ||
| } | ||
|
|
||
| return []; | ||
| } | ||
|
|
||
| const query = filters.subFilters.length ? this.getFilterQuery(resource, filters) : {}; | ||
|
|
||
| const sortArray: any[] = sort.map((s) => { | ||
| return [s.field, this.SortDirectionsMap[s.direction]]; | ||
| }); | ||
| const sortArray: any[] = sort.map((s) => [s.field, this.SortDirectionsMap[s.direction]]); | ||
|
|
||
| const result = await collection.find(query) | ||
| return await collection.find(query) | ||
| .sort(sortArray) | ||
| .skip(offset) | ||
| .limit(limit) | ||
| .toArray(); | ||
|
|
||
| return result | ||
| } | ||
|
|
||
| async getCount({ resource, filters }: { | ||
|
|
@@ -380,14 +404,22 @@ class MongoConnector extends AdminForthBaseConnector implements IAdminForthDataS | |
|
|
||
| async updateRecordOriginalValues({ resource, recordId, newValues }) { | ||
| const collection = this.client.db().collection(resource.table); | ||
| await collection.updateOne({ [this.getPrimaryKey(resource)]: recordId }, { $set: newValues }); | ||
| const pk = this.getPrimaryKey(resource); | ||
| for (const id of this.pkCandidates(recordId)) { | ||
| const res = await collection.updateOne({ [pk]: id }, { $set: newValues }); | ||
| if (res.matchedCount > 0) return; | ||
| } | ||
| throw new Error(`Record with id ${recordId} not found in resource ${resource.name}`); | ||
| } | ||
|
|
||
| async deleteRecord({ resource, recordId }): Promise<boolean> { | ||
| const primaryKey = this.getPrimaryKey(resource); | ||
| const collection = this.client.db().collection(resource.table); | ||
| const res = await collection.deleteOne({ [primaryKey]: recordId }); | ||
| return res.deletedCount > 0; | ||
| const pk = this.getPrimaryKey(resource); | ||
| for (const id of this.pkCandidates(recordId)) { | ||
| const res = await collection.deleteOne({ [pk]: id }); | ||
| if (res.deletedCount > 0) return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| async close() { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
== is very implicit, better use !v maybe or
=== null || v === undefined return v