Skip to content

Commit faa818c

Browse files
committed
wip query
1 parent a87b4f9 commit faa818c

6 files changed

Lines changed: 263 additions & 8 deletions

File tree

lib/core/CouchRepository.js

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,54 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
1414
Object.defineProperty(exports, "__esModule", { value: true });
1515
const Validation_1 = __importDefault(require("./Validation")); // Import the Validation class
1616
const BaseEntity_1 = __importDefault(require("./BaseEntity"));
17+
// Mango operators
18+
const LOGICAL_OPERATORS = new Set([
19+
'$and', '$or', '$not', '$nor', '$all', '$elemMatch', '$allMatch', '$keyMapMatch'
20+
]);
21+
const CONDITIONAL_OPERATORS = new Set([
22+
'$lt', '$lte', '$eq', '$ne', '$gte', '$gt', '$exists', '$type', '$in', '$nin',
23+
'$size', '$mod', '$regex', '$beginsWith'
24+
]);
25+
function translateSelector(selector, fieldMap) {
26+
const translatedSelector = {};
27+
for (const [key, value] of Object.entries(selector)) {
28+
if (LOGICAL_OPERATORS.has(key)) {
29+
// Logical operator (e.g., $and): recursively translate each sub-condition
30+
if (Array.isArray(value)) {
31+
translatedSelector[key] = value.map((subSelector) => translateSelector(subSelector, fieldMap));
32+
}
33+
else {
34+
translatedSelector[key] = translateSelector(value, fieldMap);
35+
}
36+
}
37+
else if (CONDITIONAL_OPERATORS.has(key)) {
38+
// Conditional operator (e.g., $eq): Keep the key and value as-is, no further translation needed
39+
translatedSelector[key] = value;
40+
}
41+
else {
42+
// Regular field name, so translate it using fieldMap
43+
const translatedField = fieldMap[key] || key;
44+
// If the value is an object and not a conditional operator, translate it recursively
45+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
46+
const innerKeys = Object.keys(value);
47+
const isConditional = innerKeys.every(k => CONDITIONAL_OPERATORS.has(k));
48+
// If all inner keys are conditional, translate the field name and keep conditions as-is
49+
if (isConditional) {
50+
translatedSelector[translatedField] = value;
51+
}
52+
else {
53+
// Otherwise, translate recursively
54+
translatedSelector[translatedField] = translateSelector(value, fieldMap);
55+
}
56+
}
57+
else {
58+
// Primitive or array value, assign directly
59+
translatedSelector[translatedField] = value;
60+
}
61+
}
62+
}
63+
return translatedSelector;
64+
}
1765
const transformToDocumentFormat = function (data, entityClass) {
1866
const transformedData = {};
1967
const fieldMap = entityClass.fieldMap || {};
@@ -86,6 +134,7 @@ const findUsingMango = function (query, entityClass, connection) {
86134
query.selector = {};
87135
}
88136
query.selector[CouchRepository.getFieldNameFromFieldMap(entityClass, 'type')] = entityClass.type;
137+
console.log(JSON.stringify(query, null, 4));
89138
// Execute the query
90139
return yield connection.find(query);
91140
}
@@ -146,7 +195,7 @@ class CouchRepository {
146195
findOne(selector) {
147196
return __awaiter(this, void 0, void 0, function* () {
148197
try {
149-
const res = yield findUsingMango({ selector }, this.entityClass, this.connection);
198+
const res = yield findUsingMango({ "selector": translateSelector(selector, this.entityClass.fieldMap) }, this.entityClass, this.connection);
150199
if (res.docs.length === 0) {
151200
return null; // No document found
152201
}
@@ -161,7 +210,7 @@ class CouchRepository {
161210
findOneOrFail(selector) {
162211
return __awaiter(this, void 0, void 0, function* () {
163212
try {
164-
const res = yield findUsingMango({ selector }, this.entityClass, this.connection);
213+
const res = yield findUsingMango({ "selector": translateSelector(selector, this.entityClass.fieldMap) }, this.entityClass, this.connection);
165214
if (res.docs.length === 0) {
166215
throw new Error("Document not found");
167216
}
@@ -176,7 +225,7 @@ class CouchRepository {
176225
findMany(selector) {
177226
return __awaiter(this, void 0, void 0, function* () {
178227
try {
179-
const res = yield findUsingMango({ selector }, this.entityClass, this.connection);
228+
const res = yield findUsingMango({ "selector": translateSelector(selector, this.entityClass.fieldMap) }, this.entityClass, this.connection);
180229
return res.docs.map((doc) => new this.entityClass(doc));
181230
}
182231
catch (err) {

src/core/CouchRepository.ts

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Nano, { DocumentScope } from "nano";
22
import Validation from './Validation'; // Import the Validation class
33
import BaseEntity from './BaseEntity';
4+
import QueryBuilder from './QueryBuilder'
45

56
// Type for the entity class constructor
67
type EntityClass = {
@@ -11,6 +12,60 @@ type EntityClass = {
1112
[key: string]: any;
1213
};
1314

15+
// Mango operators
16+
const LOGICAL_OPERATORS = new Set([
17+
'$and', '$or', '$not', '$nor', '$all', '$elemMatch', '$allMatch', '$keyMapMatch'
18+
]);
19+
20+
const CONDITIONAL_OPERATORS = new Set([
21+
'$lt', '$lte', '$eq', '$ne', '$gte', '$gt', '$exists', '$type', '$in', '$nin',
22+
'$size', '$mod', '$regex', '$beginsWith'
23+
]);
24+
25+
function translateSelector(
26+
selector: Record<string, any>,
27+
fieldMap: Record<string, string>
28+
): Record<string, any> {
29+
const translatedSelector: Record<string, any> = {};
30+
31+
for (const [key, value] of Object.entries(selector)) {
32+
if (LOGICAL_OPERATORS.has(key)) {
33+
// Logical operator (e.g., $and): recursively translate each sub-condition
34+
if (Array.isArray(value)) {
35+
translatedSelector[key] = value.map((subSelector) => translateSelector(subSelector, fieldMap));
36+
} else {
37+
translatedSelector[key] = translateSelector(value, fieldMap);
38+
}
39+
} else if (CONDITIONAL_OPERATORS.has(key)) {
40+
// Conditional operator (e.g., $eq): Keep the key and value as-is, no further translation needed
41+
translatedSelector[key] = value;
42+
} else {
43+
// Regular field name, so translate it using fieldMap
44+
const translatedField = fieldMap[key] || key;
45+
46+
// If the value is an object and not a conditional operator, translate it recursively
47+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
48+
const innerKeys = Object.keys(value);
49+
const isConditional = innerKeys.every(k => CONDITIONAL_OPERATORS.has(k));
50+
51+
// If all inner keys are conditional, translate the field name and keep conditions as-is
52+
if (isConditional) {
53+
translatedSelector[translatedField] = value;
54+
} else {
55+
// Otherwise, translate recursively
56+
translatedSelector[translatedField] = translateSelector(value, fieldMap);
57+
}
58+
} else {
59+
// Primitive or array value, assign directly
60+
translatedSelector[translatedField] = value;
61+
}
62+
}
63+
}
64+
65+
return translatedSelector;
66+
}
67+
68+
1469
const transformToDocumentFormat = function (data: Record<string, any>, entityClass: EntityClass): Record<string, any> {
1570
const transformedData: Record<string, any> = {};
1671
const fieldMap = entityClass.fieldMap || {};
@@ -99,6 +154,8 @@ const findUsingMango = async function (query: Nano.MangoQuery, entityClass: Enti
99154
}
100155
query.selector[CouchRepository.getFieldNameFromFieldMap(entityClass, 'type')] = entityClass.type;
101156

157+
console.log(JSON.stringify(query,null,4))
158+
102159
// Execute the query
103160
return await connection.find(query);
104161
} catch (err) {
@@ -166,7 +223,7 @@ abstract class CouchRepository {
166223
// 2. Find one document using a Mango selector
167224
async findOne(selector: any): Promise<BaseEntity | null> {
168225
try {
169-
const res = await findUsingMango({ selector }, this.entityClass, this.connection);
226+
const res = await findUsingMango({"selector":translateSelector(selector, this.entityClass.fieldMap)}, this.entityClass, this.connection);
170227

171228
if (res.docs.length === 0) {
172229
return null; // No document found
@@ -181,7 +238,7 @@ abstract class CouchRepository {
181238
// 3. Find one document or fail
182239
async findOneOrFail(selector: any): Promise<BaseEntity> {
183240
try {
184-
const res = await findUsingMango({ selector }, this.entityClass, this.connection);
241+
const res = await findUsingMango( {"selector":translateSelector(selector, this.entityClass.fieldMap)} , this.entityClass, this.connection);
185242

186243
if (res.docs.length === 0) {
187244
throw new Error("Document not found");
@@ -196,7 +253,7 @@ abstract class CouchRepository {
196253
// 4. Find many documents using a Mango selector
197254
async findMany(selector: any): Promise<BaseEntity[]> {
198255
try {
199-
const res = await findUsingMango({ selector }, this.entityClass, this.connection);
256+
const res = await findUsingMango({"selector":translateSelector(selector, this.entityClass.fieldMap)}, this.entityClass, this.connection);
200257

201258
return res.docs.map((doc) => new this.entityClass(doc));
202259
} catch (err) {
@@ -298,6 +355,10 @@ abstract class CouchRepository {
298355
return entityAttr;
299356
}
300357

358+
createQueryBuilder() {
359+
return new QueryBuilder();
360+
}
361+
301362
}
302363

303364
export default CouchRepository;

src/core/QueryBuilder.ts

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
import Nano, { DocumentScope } from "nano";
2+
import { QueryExpressionMap } from "./QueryExpressionMap"
3+
import { WhereClause, WhereClauseCondition } from "./WhereClause";
24

3-
class QueryBuilder {
5+
abstract class QueryBuilder {
46
private query: any; // The actual query being built (Mango query)
57
private params: Record<string, any>; // Holds the parameterized values for the query
68
private fieldsUsed: string[]; // To track the fields used for views
9+
private wheres: Object[] ;
10+
11+
expressionMap: QueryExpressionMap;
712

813
constructor() {
914
this.query = { selector: {} };
1015
this.params = {};
1116
this.fieldsUsed = [];
17+
this.expressionMap = new QueryExpressionMap();
18+
this.wheres = []
1219
}
1320

1421
// Define "where" with operators like =, !=, >, <, >=, <=
@@ -17,7 +24,8 @@ class QueryBuilder {
1724
if (!field || !operator || !param) {
1825
throw new Error('Invalid query string format. Must be "field operator value".');
1926
}
20-
27+
28+
2129
this.query.selector[field] = { [operator]: value };
2230
this.params[field] = value; // Add value to parameters
2331
this.fieldsUsed.push(field);
@@ -83,5 +91,45 @@ class QueryBuilder {
8391
return res.docs.length > 0 ? res.docs : [];
8492
}
8593
}
94+
95+
protected getWhereCondition(
96+
where:
97+
| string,
98+
): WhereClauseCondition {
99+
100+
if (typeof where === "string") {
101+
return where
102+
}
103+
104+
const wheres: any[] = Array.isArray(where) ? where : [where]
105+
const clauses: WhereClause[] = []
106+
107+
for (const where of wheres) {
108+
const conditions: WhereClauseCondition = []
109+
110+
// Filter the conditions and set up the parameter values
111+
for (const [aliasPath, parameterValue] of this.getPredicates(
112+
where,
113+
)) {
114+
conditions.push({
115+
type: "and",
116+
condition: this.getWherePredicateCondition(
117+
aliasPath,
118+
parameterValue,
119+
),
120+
})
121+
}
122+
123+
clauses.push({ type: "or", condition: conditions })
124+
}
125+
126+
if (clauses.length === 1) {
127+
return clauses[0].condition
128+
}
129+
130+
return clauses
131+
}
86132
}
133+
134+
export default QueryBuilder;
87135

src/core/QueryExpressionMap.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import QueryBuilder from "./QueryBuilder"
2+
import { WhereClause } from "./WhereClause"
3+
4+
/**
5+
* Contains all properties of the QueryBuilder that needs to be build a final query.
6+
*/
7+
export class QueryExpressionMap {
8+
9+
/**
10+
* WHERE queries.
11+
*/
12+
wheres: WhereClause[] = []
13+
14+
// -------------------------------------------------------------------------
15+
// Constructor
16+
// -------------------------------------------------------------------------
17+
18+
constructor() {
19+
20+
}
21+
22+
23+
}

src/core/SelectQueryBuilder.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import QueryBuilder from "./QueryBuilder";
2+
3+
export class SelectQueryBuilder extends QueryBuilder {
4+
5+
/**
6+
* Adds new AND WHERE condition in the query builder.
7+
* Additionally you can add parameters used in where expression.
8+
*/
9+
andWhere(
10+
where:
11+
| string,
12+
parameters?: any,
13+
): this {
14+
this.expressionMap.wheres.push({
15+
type: "and",
16+
condition: this.getWhereCondition(where),
17+
})
18+
if (parameters) this.setParameters(parameters)
19+
return this
20+
}
21+
22+
/**
23+
* Adds new OR WHERE condition in the query builder.
24+
* Additionally you can add parameters used in where expression.
25+
*/
26+
orWhere(
27+
where:
28+
| string,
29+
parameters?: any,
30+
): this {
31+
this.expressionMap.wheres.push({
32+
type: "or",
33+
condition: this.getWhereCondition(where),
34+
})
35+
if (parameters) this.setParameters(parameters)
36+
return this
37+
}
38+
39+
}

src/core/WhereClause.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
type WrappingOperator = "not"
2+
3+
type PredicateOperator =
4+
| "lessThan"
5+
| "lessThanOrEqual"
6+
| "moreThan"
7+
| "moreThanOrEqual"
8+
| "equal"
9+
| "notEqual"
10+
| "and"
11+
| "or"
12+
13+
export interface WherePredicateOperator {
14+
operator: PredicateOperator
15+
16+
parameters: string[]
17+
}
18+
19+
export interface WhereWrappingOperator {
20+
operator: WrappingOperator
21+
22+
condition: WhereClauseCondition
23+
}
24+
25+
export interface WhereClause {
26+
type: "simple" | "and" | "or"
27+
28+
condition: WhereClauseCondition
29+
}
30+
31+
export type WhereClauseCondition =
32+
| string
33+
| WherePredicateOperator
34+
| WhereWrappingOperator
35+
| WhereClause[]

0 commit comments

Comments
 (0)