Skip to content

Commit

Permalink
Json db doesn't respect dbName option on field #72
Browse files Browse the repository at this point in the history
  • Loading branch information
noam-honig committed Sep 20, 2023
1 parent 4de0e0c commit f0b05f5
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 54 deletions.
117 changes: 73 additions & 44 deletions projects/core/src/data-providers/array-entity-data-provider.ts
Expand Up @@ -4,6 +4,11 @@ import type {
EntityDataProvider,
EntityDataProviderFindOptions,
} from '../data-interfaces'
import {
type EntityDbNamesBase,
dbNamesOf,
isDbReadonly,
} from '../filter/filter-consumer-bridge-to-sql-request'
import type { FilterConsumer } from '../filter/filter-interfaces'
import { Filter, customDatabaseFilterToken } from '../filter/filter-interfaces'
import type { EntityFilter, EntityMetadata } from '../remult3/remult3'
Expand All @@ -22,33 +27,41 @@ export class ArrayEntityDataProvider implements EntityDataProvider {
) {
if (!rows) rows = []
else {
for (const r of rows) {
this.verifyThatRowHasAllNotNullColumns(r)
}
}
}
private verifyThatRowHasAllNotNullColumns(r: any) {
__names: EntityDbNamesBase
async init() {
if (this.__names) return this.__names
this.__names = await dbNamesOf(this.entity)
for (const r of this.rows) {
this.verifyThatRowHasAllNotNullColumns(r, this.__names)
}
return this.__names
}
private verifyThatRowHasAllNotNullColumns(r: any, names: EntityDbNamesBase) {
for (const f of this.entity.fields) {
const key = names.$dbNameOf(f)
if (!f.isServerExpression)
if (!f.allowNull) {
if (r[f.key] === undefined || r[f.key] === null) {
if (r[key] === undefined || r[key] === null) {
let val = undefined
if (f.valueType === Boolean) val = false
else if (f.valueType === Number) val = 0
else if (f.valueType === String) val = ''
r[f.key] = val
r[key] = val
}
} else if (r[f.key] === undefined) r[f.key] = null
} else if (r[key] === undefined) r[key] = null
}
}
async count(where?: Filter): Promise<number> {
let rows = this.rows
const names = await this.init()
let j = 0
for (let i = 0; i < rows.length; i++) {
if (!where) {
j++
} else {
let x = new FilterConsumerBridgeToObject(rows[i])
let x = new FilterConsumerBridgeToObject(rows[i], names)
where.__applyToConsumer(x)
if (x.ok) j++
}
Expand All @@ -57,74 +70,78 @@ export class ArrayEntityDataProvider implements EntityDataProvider {
}
async find(options?: EntityDataProviderFindOptions): Promise<any[]> {
let rows = this.rows
const dbNames = await this.init()
if (options) {
if (options.where) {
rows = rows.filter((i) => {
let x = new FilterConsumerBridgeToObject(i)
let x = new FilterConsumerBridgeToObject(i, dbNames)
options.where.__applyToConsumer(x)
return x.ok
})
}
if (options.orderBy) {
rows = rows.sort((a: any, b: any) => {
return options.orderBy.compare(a, b)
return options.orderBy.compare(a, b, dbNames.$dbNameOf)
})
}
rows = pageArray(rows, options)
}
if (rows)
return rows.map((i) => {
return this.translateFromJson(i)
return this.translateFromJson(i, dbNames)
})
}
translateFromJson(row: any) {
translateFromJson(row: any, dbNames: EntityDbNamesBase) {
let result = {}
for (const col of this.entity.fields) {
result[col.key] = col.valueConverter.fromJson(row[col.key])
result[col.key] = col.valueConverter.fromJson(row[dbNames.$dbNameOf(col)])
}
return result
}
translateToJson(row: any) {
translateToJson(row: any, dbNames: EntityDbNamesBase) {
let result = {}
for (const col of this.entity.fields) {
result[col.key] = col.valueConverter.toJson(row[col.key])
if (!isDbReadonly(col, dbNames))
result[dbNames.$dbNameOf(col)] = col.valueConverter.toJson(row[col.key])
}
return result
}

private idMatches(id: any): (item: any) => boolean {
private idMatches(id: any, names: EntityDbNamesBase): (item: any) => boolean {
return (item) => {
let x = new FilterConsumerBridgeToObject(item)
let x = new FilterConsumerBridgeToObject(item, names)
Filter.fromEntityFilter(
this.entity,
this.entity.idMetadata.getIdFilter(id),
).__applyToConsumer(x)
return x.ok
}
}
public update(id: any, data: any): Promise<any> {
let idMatches = this.idMatches(id)
async update(id: any, data: any): Promise<any> {
const names = await this.init()
let idMatches = this.idMatches(id, names)
let keys = Object.keys(data)
for (let i = 0; i < this.rows.length; i++) {
let r = this.rows[i]
if (idMatches(r)) {
let newR = { ...r }
for (const f of this.entity.fields) {
if (!f.dbReadOnly && !f.isServerExpression) {
if (!isDbReadonly(f, names)) {
if (keys.includes(f.key)) {
newR[f.key] = f.valueConverter.toJson(data[f.key])
newR[names.$dbNameOf(f)] = f.valueConverter.toJson(data[f.key])
}
}
}
this.verifyThatRowHasAllNotNullColumns(newR)
this.verifyThatRowHasAllNotNullColumns(newR, names)
this.rows[i] = newR
return Promise.resolve(this.translateFromJson(this.rows[i]))
return Promise.resolve(this.translateFromJson(this.rows[i], names))
}
}
throw new Error("couldn't find id to update: " + id)
}
public delete(id: any): Promise<void> {
let idMatches = this.idMatches(id)
async delete(id: any): Promise<void> {
const names = await this.init()
let idMatches = this.idMatches(id, names)
for (let i = 0; i < this.rows.length; i++) {
if (idMatches(this.rows[i])) {
this.rows.splice(i, 1)
Expand All @@ -134,23 +151,24 @@ export class ArrayEntityDataProvider implements EntityDataProvider {
throw new Error("couldn't find id to delete: " + id)
}
async insert(data: any): Promise<any> {
const names = await this.init()
let j = this.translateToJson(data, names)
let idf = this.entity.idMetadata.field
if (!(idf instanceof CompoundIdField)) {
if (idf.options.valueConverter?.fieldTypeInDb === 'autoincrement') {
data[idf.key] = 1
j[idf.key] = 1
for (const row of this.rows) {
if (row[idf.key] >= data[idf.key]) data[idf.key] = row[idf.key] + 1
if (row[idf.key] >= j[idf.key]) j[idf.key] = row[idf.key] + 1
}
}
if (data[idf.key])
if (j[idf.key])
this.rows.forEach((i) => {
if (data[idf.key] == i[idf.key]) throw Error('id already exists')
if (j[idf.key] == i[idf.key]) throw Error('id already exists')
})
}
let j = this.translateToJson(data)
this.verifyThatRowHasAllNotNullColumns(j)
this.verifyThatRowHasAllNotNullColumns(j, names)
this.rows.push(j)
return Promise.resolve(this.translateFromJson(j))
return Promise.resolve(this.translateFromJson(j, names))
}
}
function pageArray(rows: any[], options?: EntityDataProviderFindOptions) {
Expand All @@ -169,7 +187,10 @@ function pageArray(rows: any[], options?: EntityDataProviderFindOptions) {
}
class FilterConsumerBridgeToObject implements FilterConsumer {
ok = true
constructor(private row: any) {}
constructor(
private row: any,
private dbNames: EntityDbNamesBase,
) {}
databaseCustom(databaseCustom: CustomArrayFilterObject): void {
if (databaseCustom && databaseCustom.arrayFilter) {
if (!databaseCustom.arrayFilter(this.row)) this.ok = false
Expand All @@ -180,7 +201,7 @@ class FilterConsumerBridgeToObject implements FilterConsumer {
}
or(orElements: Filter[]) {
for (const element of orElements) {
let filter = new FilterConsumerBridgeToObject(this.row)
let filter = new FilterConsumerBridgeToObject(this.row, this.dbNames)
element.__applyToConsumer(filter)
if (filter.ok) {
return
Expand All @@ -189,44 +210,52 @@ class FilterConsumerBridgeToObject implements FilterConsumer {
this.ok = false
}
isNull(col: FieldMetadata): void {
if (this.row[col.key] != null) this.ok = false
if (this.row[this.dbNames.$dbNameOf(col)] != null) this.ok = false
}
isNotNull(col: FieldMetadata): void {
if (this.row[col.key] == null) this.ok = false
if (this.row[this.dbNames.$dbNameOf(col)] == null) this.ok = false
}
isIn(col: FieldMetadata, val: any[]): void {
for (const v of val) {
if (this.row[col.key] == col.valueConverter.toJson(v)) {
if (
this.row[this.dbNames.$dbNameOf(col)] == col.valueConverter.toJson(v)
) {
return
}
}
this.ok = false
}
public isEqualTo(col: FieldMetadata, val: any): void {
if (this.row[col.key] != col.valueConverter.toJson(val)) this.ok = false
if (this.row[this.dbNames.$dbNameOf(col)] != col.valueConverter.toJson(val))
this.ok = false
}

public isDifferentFrom(col: FieldMetadata, val: any): void {
if (this.row[col.key] == col.valueConverter.toJson(val)) this.ok = false
if (this.row[this.dbNames.$dbNameOf(col)] == col.valueConverter.toJson(val))
this.ok = false
}

public isGreaterOrEqualTo(col: FieldMetadata, val: any): void {
if (this.row[col.key] < col.valueConverter.toJson(val)) this.ok = false
if (this.row[this.dbNames.$dbNameOf(col)] < col.valueConverter.toJson(val))
this.ok = false
}

public isGreaterThan(col: FieldMetadata, val: any): void {
if (this.row[col.key] <= col.valueConverter.toJson(val)) this.ok = false
if (this.row[this.dbNames.$dbNameOf(col)] <= col.valueConverter.toJson(val))
this.ok = false
}

public isLessOrEqualTo(col: FieldMetadata, val: any): void {
if (this.row[col.key] > col.valueConverter.toJson(val)) this.ok = false
if (this.row[this.dbNames.$dbNameOf(col)] > col.valueConverter.toJson(val))
this.ok = false
}

public isLessThan(col: FieldMetadata, val: any): void {
if (this.row[col.key] >= col.valueConverter.toJson(val)) this.ok = false
if (this.row[this.dbNames.$dbNameOf(col)] >= col.valueConverter.toJson(val))
this.ok = false
}
public containsCaseInsensitive(col: FieldMetadata, val: any): void {
let v = this.row[col.key]
let v = this.row[this.dbNames.$dbNameOf(col)]
if (!v) {
this.ok = false
return
Expand Down
7 changes: 4 additions & 3 deletions projects/core/src/sort.ts
Expand Up @@ -22,12 +22,13 @@ export class Sort {
}
return r
}
compare(a: any, b: any) {
compare(a: any, b: any, getFieldKey?: (field: FieldMetadata) => string) {
if (!getFieldKey) getFieldKey = (x) => x.key
let r = 0
for (let i = 0; i < this.Segments.length; i++) {
let seg = this.Segments[i]
let left = a[seg.field.key]
let right = b[seg.field.key]
let left = a[getFieldKey(seg.field)]
let right = b[getFieldKey(seg.field)]
if (left > right) r = 1
else if (left < right) r = -1
if (r != 0) {
Expand Down
71 changes: 68 additions & 3 deletions projects/tests/dbs/inMemory.spec.ts
@@ -1,10 +1,16 @@
import { beforeEach, describe } from 'vitest'
import { beforeEach, describe, expect, it } from 'vitest'
import type { DataProvider } from '../../core'
import { InMemoryDataProvider, Remult } from '../../core'
import {
Entity,
Fields,
InMemoryDataProvider,
Remult,
describeClass,
} from '../../core'
import { allDbTests } from './shared-tests'

describe('In Memory Tests', () => {
var db: DataProvider
var db: InMemoryDataProvider
let remult: Remult

beforeEach(() => {
Expand All @@ -26,4 +32,63 @@ describe('In Memory Tests', () => {
excludeLiveQuery: true,
},
)
it("test doesn't store server expressions and db readonly", async () => {
const c = class {
id = 0
a = ''
b = ''
c = ''
}
describeClass(c, Entity('c'), {
id: Fields.number(),
a: Fields.string(),
b: Fields.string({ serverExpression: () => 'x' }),
c: Fields.string({ dbReadOnly: true }),
})
await remult.repo(c).insert({ id: 1, a: 'a', b: 'b', c: 'c' })
expect(db.rows).toMatchInlineSnapshot(`
{
"c": [
{
"a": "a",
"c": "",
"id": 1,
},
],
}
`)
})
it('test db names are respected', async () => {
const c = class {
id = 0
a = ''
}
describeClass(c, Entity('c'), {
id: Fields.number(),
a: Fields.string({ dbName: 'aaa' }),
})
await remult.repo(c).insert({ id: 1, a: 'a' })
expect(db.rows).toMatchInlineSnapshot(`
{
"c": [
{
"aaa": "a",
"id": 1,
},
],
}
`)
expect(await remult.repo(c).count({ a: 'a' })).toBe(1)
await remult.repo(c).update(1, { a: 'b' })
expect(db.rows).toMatchInlineSnapshot(`
{
"c": [
{
"aaa": "b",
"id": 1,
},
],
}
`)
})
})

0 comments on commit f0b05f5

Please sign in to comment.