Browse Source

Merge pull request #6470 from nocodb/refactor/new-data-apis

New data apis - followup
pull/6431/head
Pranav C 12 months ago committed by GitHub
parent
commit
492d57efd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 427
      packages/nocodb-sdk/src/lib/Api.ts
  2. 54
      packages/nocodb/src/db/BaseModelSqlv2.ts
  3. 30
      packages/nocodb/src/db/CustomKnex.ts
  4. 892
      packages/nocodb/src/schema/swagger.json
  5. 18
      packages/nocodb/src/services/data-table.service.ts
  6. 19
      packages/nocodb/src/utils/common/NcConnectionMgrv2.ts
  7. 206
      packages/nocodb/tests/unit/rest/tests/newDataApis.test.ts
  8. 6
      packages/nocodb/tests/unit/rest/tests/tableRow.test.ts
  9. 10
      tests/playwright/tests/db/features/verticalFillHandle.spec.ts
  10. 16
      tests/playwright/tests/db/features/webhook.spec.ts
  11. 4
      tests/playwright/tests/utils/general.ts

427
packages/nocodb-sdk/src/lib/Api.ts

@ -10432,4 +10432,431 @@ export class Api<
...params,
}),
};
dbDataTableRow = {
/**
* @description List all table rows in a given table
*
* @tags DB Data Table Row
* @name List
* @summary List Table Rows
* @request GET:/api/v1/tables/{tableId}/rows
* @response `200` `{
\** List of data objects *\
list: (object)[],
\** Paginated Info *\
pageInfo: PaginatedType,
}` OK
* @response `400` `{
\** @example BadRequest [Error]: <ERROR MESSAGE> *\
msg: string,
}`
*/
list: (
tableId: string,
query: {
/** View ID */
viewId: string;
/** Which fields to be shown */
fields?: any[];
/** The result will be sorted based on `sort` query */
sort?: string[] | string;
/** Extra filtering */
where?: string;
/**
* Offset in rows
* @min 0
*/
offset?: number;
/**
* Limit in rows
* @min 1
*/
limit?: number;
/** Used for multiple sort queries */
sortArrJson?: string;
/** Used for multiple filter queries */
filterArrJson?: string;
},
params: RequestParams = {}
) =>
this.request<
{
/** List of data objects */
list: object[];
/** Paginated Info */
pageInfo: PaginatedType;
},
{
/** @example BadRequest [Error]: <ERROR MESSAGE> */
msg: string;
}
>({
path: `/api/v1/tables/${tableId}/rows`,
method: 'GET',
query: query,
format: 'json',
...params,
}),
/**
* @description Create a new row in a given table and project.
*
* @tags DB Data Table Row
* @name Create
* @summary Create Table Rows
* @request POST:/api/v1/tables/{tableId}/rows
* @response `200` `any` OK
* @response `400` `{
\** @example BadRequest [Error]: <ERROR MESSAGE> *\
msg: string,
}`
*/
create: (
tableId: string,
query: {
/** View ID */
viewId: string;
},
data: object | object[],
params: RequestParams = {}
) =>
this.request<
any,
{
/** @example BadRequest [Error]: <ERROR MESSAGE> */
msg: string;
}
>({
path: `/api/v1/tables/${tableId}/rows`,
method: 'POST',
query: query,
body: data,
type: ContentType.Json,
format: 'json',
...params,
}),
/**
* @description Create a new row in a given table and project.
*
* @tags DB Data Table Row
* @name Update
* @summary Update Table Rows
* @request PUT:/api/v1/tables/{tableId}/rows
* @response `200` `any` OK
* @response `400` `{
\** @example BadRequest [Error]: <ERROR MESSAGE> *\
msg: string,
}`
*/
update: (
tableId: string,
query: {
/** View ID */
viewId: string;
},
data: object | object[],
params: RequestParams = {}
) =>
this.request<
any,
{
/** @example BadRequest [Error]: <ERROR MESSAGE> */
msg: string;
}
>({
path: `/api/v1/tables/${tableId}/rows`,
method: 'PUT',
query: query,
body: data,
type: ContentType.Json,
format: 'json',
...params,
}),
/**
* @description Create a new row in a given table and project.
*
* @tags DB Data Table Row
* @name Delete
* @summary Delete Table Rows
* @request DELETE:/api/v1/tables/{tableId}/rows
* @response `200` `any` OK
* @response `400` `{
\** @example BadRequest [Error]: <ERROR MESSAGE> *\
msg: string,
}`
*/
delete: (
tableId: string,
query: {
/** View ID */
viewId: string;
},
data: object | object[],
params: RequestParams = {}
) =>
this.request<
any,
{
/** @example BadRequest [Error]: <ERROR MESSAGE> */
msg: string;
}
>({
path: `/api/v1/tables/${tableId}/rows`,
method: 'DELETE',
query: query,
body: data,
type: ContentType.Json,
format: 'json',
...params,
}),
/**
* @description Get table row in a given table
*
* @tags DB Data Table Row
* @name Read
* @summary Read Table Row
* @request GET:/api/v1/tables/{tableId}/rows/{rowId}
* @response `200` `object` OK
* @response `400` `{
\** @example BadRequest [Error]: <ERROR MESSAGE> *\
msg: string,
}`
*/
read: (
tableId: string,
rowId: string,
query: {
/** View ID */
viewId: string;
/** Which fields to be shown */
fields?: any[];
/**
* Offset in rows
* @min 0
*/
offset?: number;
},
params: RequestParams = {}
) =>
this.request<
object,
{
/** @example BadRequest [Error]: <ERROR MESSAGE> */
msg: string;
}
>({
path: `/api/v1/tables/${tableId}/rows/${rowId}`,
method: 'GET',
query: query,
format: 'json',
...params,
}),
/**
* @description Count of rows in a given table
*
* @tags DB Data Table Row
* @name Count
* @summary Table Rows Count
* @request GET:/api/v1/tables/{tableId}/rows/count
* @response `200` `{
count?: number,
}` OK
* @response `400` `{
\** @example BadRequest [Error]: <ERROR MESSAGE> *\
msg: string,
}`
*/
count: (
tableId: string,
query: {
/** View ID */
viewId: string;
/** Which fields to be shown */
fields?: any[];
/** Extra filtering */
where?: string;
/** Used for multiple filter queries */
filterArrJson?: string;
},
params: RequestParams = {}
) =>
this.request<
{
count?: number;
},
{
/** @example BadRequest [Error]: <ERROR MESSAGE> */
msg: string;
}
>({
path: `/api/v1/tables/${tableId}/rows/count`,
method: 'GET',
query: query,
format: 'json',
...params,
}),
/**
* @description Linked rows in a given Links/LinkToAnotherRecord column
*
* @tags DB Data Table Row
* @name NestedList
* @summary Get Nested Relations Rows
* @request GET:/api/v1/tables/{tableId}/links/{columnId}/rows/{rowId}
* @response `200` `{
\** List of data objects *\
list: (object)[],
\** Paginated Info *\
pageInfo: PaginatedType,
}` OK
* @response `400` `{
\** @example BadRequest [Error]: <ERROR MESSAGE> *\
msg: string,
}`
*/
nestedList: (
tableId: string,
columnId: string,
rowId: string,
query: {
/** View ID */
viewId: string;
/** Which fields to be shown */
fields?: any[];
/** The result will be sorted based on `sort` query */
sort?: string[] | string;
/** Extra filtering */
where?: string;
/**
* Offset in rows
* @min 0
*/
offset?: number;
/**
* Limit in rows
* @min 1
*/
limit?: number;
/** Used for multiple sort queries */
sortArrJson?: string;
/** Used for multiple filter queries */
filterArrJson?: string;
},
params: RequestParams = {}
) =>
this.request<
{
/** List of data objects */
list: object[];
/** Paginated Info */
pageInfo: PaginatedType;
},
{
/** @example BadRequest [Error]: <ERROR MESSAGE> */
msg: string;
}
>({
path: `/api/v1/tables/${tableId}/links/${columnId}/rows/${rowId}`,
method: 'GET',
query: query,
format: 'json',
...params,
}),
/**
* @description Create a link with the row.
*
* @tags DB Data Table Row
* @name NestedLink
* @summary Create Nested Relations Rows
* @request POST:/api/v1/tables/{tableId}/links/{columnId}/rows/{rowId}
* @response `200` `any` OK
* @response `400` `{
\** @example BadRequest [Error]: <ERROR MESSAGE> *\
msg: string,
}`
*/
nestedLink: (
tableId: string,
columnId: string,
rowId: string,
query: {
/** View ID */
viewId: string;
},
data: object | object[],
params: RequestParams = {}
) =>
this.request<
any,
{
/** @example BadRequest [Error]: <ERROR MESSAGE> */
msg: string;
}
>({
path: `/api/v1/tables/${tableId}/links/${columnId}/rows/${rowId}`,
method: 'POST',
query: query,
body: data,
type: ContentType.Json,
format: 'json',
...params,
}),
/**
* @description Create a new row in a given table and project.
*
* @tags DB Data Table Row
* @name NestedUnlink
* @summary Delete Nested Relations Rows
* @request DELETE:/api/v1/tables/{tableId}/links/{columnId}/rows/{rowId}
* @response `200` `any` OK
* @response `400` `{
\** @example BadRequest [Error]: <ERROR MESSAGE> *\
msg: string,
}`
*/
nestedUnlink: (
tableId: string,
columnId: string,
rowId: string,
query: {
/** View ID */
viewId: string;
},
data: object | object[],
params: RequestParams = {}
) =>
this.request<
any,
{
/** @example BadRequest [Error]: <ERROR MESSAGE> */
msg: string;
}
>({
path: `/api/v1/tables/${tableId}/links/${columnId}/rows/${rowId}`,
method: 'DELETE',
query: query,
body: data,
type: ContentType.Json,
format: 'json',
...params,
}),
};
}

54
packages/nocodb/src/db/BaseModelSqlv2.ts

@ -53,6 +53,7 @@ import {
} from '~/utils/globals';
dayjs.extend(utc);
dayjs.extend(timezone);
const GROUP_COL = '__nc_group_id';
@ -2478,6 +2479,7 @@ class BaseModelSqlv2 {
skip_hooks = false,
raw = false,
insertOneByOneAsFallback = false,
isSingleRecordInsertion = false,
}: {
chunkSize?: number;
cookie?: any;
@ -2485,6 +2487,7 @@ class BaseModelSqlv2 {
skip_hooks?: boolean;
raw?: boolean;
insertOneByOneAsFallback?: boolean;
isSingleRecordInsertion?: boolean;
} = {},
) {
let trx;
@ -2671,8 +2674,14 @@ class BaseModelSqlv2 {
await trx.commit();
if (!raw && !skip_hooks)
await this.afterBulkInsert(insertDatas, this.dbDriver, cookie);
if (!raw && !skip_hooks) {
if (isSingleRecordInsertion) {
const insertData = await this.readByPk(response[0]);
await this.afterInsert(insertData, this.dbDriver, cookie);
} else {
await this.afterBulkInsert(insertDatas, this.dbDriver, cookie);
}
}
return response;
} catch (e) {
@ -2688,7 +2697,13 @@ class BaseModelSqlv2 {
cookie,
raw = false,
throwExceptionIfNotExist = false,
}: { cookie?: any; raw?: boolean; throwExceptionIfNotExist?: boolean } = {},
isSingleRecordUpdation = false,
}: {
cookie?: any;
raw?: boolean;
throwExceptionIfNotExist?: boolean;
isSingleRecordUpdation?: boolean;
} = {},
) {
let transaction;
try {
@ -2740,8 +2755,19 @@ class BaseModelSqlv2 {
}
}
if (!raw)
await this.afterBulkUpdate(prevData, newData, this.dbDriver, cookie);
if (!raw) {
if (isSingleRecordUpdation) {
await this.afterUpdate(
prevData[0],
newData[0],
null,
cookie,
datas[0],
);
} else {
await this.afterBulkUpdate(prevData, newData, this.dbDriver, cookie);
}
}
return res;
} catch (e) {
@ -2816,7 +2842,12 @@ class BaseModelSqlv2 {
{
cookie,
throwExceptionIfNotExist = false,
}: { cookie?: any; throwExceptionIfNotExist?: boolean } = {},
isSingleRecordDeletion = false,
}: {
cookie?: any;
throwExceptionIfNotExist?: boolean;
isSingleRecordDeletion?: boolean;
} = {},
) {
let transaction;
try {
@ -2918,7 +2949,11 @@ class BaseModelSqlv2 {
await transaction.commit();
await this.afterBulkDelete(deleted, this.dbDriver, cookie);
if (isSingleRecordDeletion) {
await this.afterDelete(deleted[0], null, cookie);
} else {
await this.afterBulkDelete(deleted, this.dbDriver, cookie);
}
return res;
} catch (e) {
@ -4618,6 +4653,11 @@ function applyPaginate(
}
export function _wherePk(primaryKeys: Column[], id: unknown | unknown[]) {
// if id object is provided use as it is
if (id && typeof id === 'object') {
return id;
}
const ids = Array.isArray(id) ? id : (id + '').split('___');
const where = {};
for (let i = 0; i < primaryKeys.length; ++i) {

30
packages/nocodb/src/db/CustomKnex.ts

@ -1,25 +1,43 @@
import { Knex, knex } from 'knex';
import { SnowflakeClient } from 'nc-help';
import { types } from 'pg';
import { defaults, types } from 'pg';
import dayjs from 'dayjs';
import type { FilterType } from 'nocodb-sdk';
import type { BaseModelSql } from '~/db/BaseModelSql';
import Filter from '~/models/Filter';
// For the code, check out
// https://raw.githubusercontent.com/brianc/node-pg-types/master/lib/builtins.js
// refer : https://github.com/brianc/node-pg-types/blob/master/lib/builtins.js
const pgTypes = {
FLOAT4: 700,
FLOAT8: 701,
DATE: 1082,
TIMESTAMP: 1114,
TIMESTAMPTZ: 1184,
NUMERIC: 1700,
};
// override parsing date column to Date()
types.setTypeParser(1082, (val) => val);
types.setTypeParser(pgTypes.DATE, (val) => val);
// override timestamp
types.setTypeParser(1114, (val) => {
types.setTypeParser(pgTypes.TIMESTAMP, (val) => {
return dayjs.utc(val).format('YYYY-MM-DD HH:mm:ssZ');
});
// override timestampz
types.setTypeParser(1184, (val) => {
types.setTypeParser(pgTypes.TIMESTAMPTZ, (val) => {
return dayjs(val).utc().format('YYYY-MM-DD HH:mm:ssZ');
});
const parseFloatVal = (val: string) => {
return parseFloat(val);
};
// parse integer values
defaults.parseInt8 = true;
// parse float values
types.setTypeParser(pgTypes.FLOAT8, parseFloatVal);
types.setTypeParser(pgTypes.NUMERIC, parseFloatVal);
const opMappingGen = {
eq: '=',
lt: '<',

892
packages/nocodb/src/schema/swagger.json

@ -15337,6 +15337,896 @@
}
]
}
},
"/api/v1/tables/{tableId}/rows": {
"parameters": [
{
"schema": {
"type": "string"
},
"name": "tableId",
"in": "path",
"required": true,
"description": "Table ID"
},
{
"schema": {
"type": "string"
},
"name": "viewId",
"in": "query",
"required": true,
"description": "View ID"
}
],
"get": {
"summary": "List Table Rows",
"operationId": "db-data-table-row-list",
"description": "List all table rows in a given table",
"tags": [
"DB Data Table Row"
],
"parameters": [
{
"schema": {
"type": "array"
},
"in": "query",
"name": "fields",
"description": "Which fields to be shown"
},
{
"schema": {
"oneOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "string"
}
]
},
"in": "query",
"name": "sort",
"description": "The result will be sorted based on `sort` query"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "where",
"description": "Extra filtering"
},
{
"schema": {
"type": "integer",
"minimum": 0
},
"in": "query",
"name": "offset",
"description": "Offset in rows"
},
{
"schema": {
"type": "integer",
"minimum": 1
},
"in": "query",
"name": "limit",
"description": "Limit in rows"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "sortArrJson",
"description": "Used for multiple sort queries"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "filterArrJson",
"description": "Used for multiple filter queries"
},
{
"$ref": "#/components/parameters/xc-auth"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"list": {
"type": "array",
"description": "List of data objects",
"items": {
"type": "object"
}
},
"pageInfo": {
"$ref": "#/components/schemas/Paginated",
"description": "Paginated Info"
}
},
"required": [
"list",
"pageInfo"
]
},
"examples": {
"Example 1": {
"value": {
"list": [
{
"Id": 1,
"Title": "baz",
"SingleSelect": null,
"Sheet-1 List": [
{
"Id": 1,
"Title": "baz"
}
],
"LTAR": [
{
"Id": 1,
"Title": "baz"
}
]
},
{
"Id": 2,
"Title": "foo",
"SingleSelect": "a",
"Sheet-1 List": [
{
"Id": 2,
"Title": "foo"
}
],
"LTAR": [
{
"Id": 2,
"Title": "foo"
}
]
},
{
"Id": 3,
"Title": "bar",
"SingleSelect": "b",
"Sheet-1 List": [],
"LTAR": []
}
],
"pageInfo": {
"totalRows": 3,
"page": 1,
"pageSize": 25,
"isFirstPage": true,
"isLastPage": true
}
}
}
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
}
}
},
"post": {
"summary": "Create Table Rows",
"operationId": "db-data-table-row-create",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
}
},
"tags": [
"DB Data Table Row"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"type": "object"
},
{
"type": "array",
"items": {
"type": "object"
}
}
]
},
"examples": {
"Example 1": {
"value": {
"Id": 1,
"Title": "foo",
"CreatedAt": "2023-03-11T09:10:53.567Z",
"UpdatedAt": "2023-03-11T09:10:53.567Z"
}
}
}
}
}
},
"description": "Create a new row in a given table and project.",
"parameters": [
{
"$ref": "#/components/parameters/xc-auth"
}
]
},
"put": {
"summary": "Update Table Rows",
"operationId": "db-data-table-row-update",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
}
},
"tags": [
"DB Data Table Row"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"type": "object"
},
{
"type": "array",
"items": {
"type": "object"
}
}
]
},
"examples": {
"Example 1": {
"value": {
"Id": 1,
"Title": "foo",
"CreatedAt": "2023-03-11T09:10:53.567Z",
"UpdatedAt": "2023-03-11T09:10:53.567Z"
}
}
}
}
}
},
"description": "Create a new row in a given table and project.",
"parameters": [
{
"$ref": "#/components/parameters/xc-auth"
}
]
},
"delete": {
"summary": "Delete Table Rows",
"operationId": "db-data-table-row-delete",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
}
},
"tags": [
"DB Data Table Row"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"type": "object"
},
{
"type": "array",
"items": {
"type": "object"
}
}
]
},
"examples": {
"Example 1": {
"value": [
{
"Id": 1
}
]
}
}
}
}
},
"description": "Create a new row in a given table and project.",
"parameters": [
{
"$ref": "#/components/parameters/xc-auth"
}
]
}
},
"/api/v1/tables/{tableId}/rows/{rowId}": {
"parameters": [
{
"schema": {
"type": "string"
},
"name": "tableId",
"in": "path",
"required": true,
"description": "Table ID"
},
{
"schema": {
"type": "string"
},
"name": "rowId",
"in": "path",
"required": true,
"description": "Row ID"
},
{
"schema": {
"type": "string"
},
"name": "viewId",
"in": "query",
"required": true,
"description": "View ID"
}
],
"get": {
"summary": "Read Table Row",
"operationId": "db-data-table-row-read",
"description": "Get table row in a given table",
"tags": [
"DB Data Table Row"
],
"parameters": [
{
"schema": {
"type": "array"
},
"in": "query",
"name": "fields",
"description": "Which fields to be shown"
},
{
"schema": {
"type": "integer",
"minimum": 0
},
"in": "query",
"name": "offset",
"description": "Offset in rows"
},
{
"$ref": "#/components/parameters/xc-auth"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object"
},
"examples": {
"Example 1": {
"value": {
"Id": 1,
"Title": "baz",
"SingleSelect": null,
"Sheet-1 List": [
{
"Id": 1,
"Title": "baz"
}
],
"LTAR": [
{
"Id": 1,
"Title": "baz"
}
]
}
}
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
}
}
}
},
"/api/v1/tables/{tableId}/rows/count": {
"parameters": [
{
"schema": {
"type": "string"
},
"name": "tableId",
"in": "path",
"required": true,
"description": "Table ID"
},
{
"schema": {
"type": "string"
},
"name": "viewId",
"in": "query",
"required": true,
"description": "View ID"
}
],
"get": {
"summary": "Table Rows Count",
"operationId": "db-data-table-row-count",
"description": "Count of rows in a given table",
"tags": [
"DB Data Table Row"
],
"parameters": [
{
"schema": {
"type": "array"
},
"in": "query",
"name": "fields",
"description": "Which fields to be shown"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "where",
"description": "Extra filtering"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "filterArrJson",
"description": "Used for multiple filter queries"
},
{
"$ref": "#/components/parameters/xc-auth"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"count": {
"type": "number"
}
},
"required": [
"list",
"pageInfo"
]
},
"examples": {
"Example 1": {
"value": {
"list": [
{
"Id": 1,
"Title": "baz",
"SingleSelect": null,
"Sheet-1 List": [
{
"Id": 1,
"Title": "baz"
}
],
"LTAR": [
{
"Id": 1,
"Title": "baz"
}
]
},
{
"Id": 2,
"Title": "foo",
"SingleSelect": "a",
"Sheet-1 List": [
{
"Id": 2,
"Title": "foo"
}
],
"LTAR": [
{
"Id": 2,
"Title": "foo"
}
]
},
{
"Id": 3,
"Title": "bar",
"SingleSelect": "b",
"Sheet-1 List": [],
"LTAR": []
}
],
"pageInfo": {
"totalRows": 3,
"page": 1,
"pageSize": 25,
"isFirstPage": true,
"isLastPage": true
}
}
}
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
}
}
}
},
"/api/v1/tables/{tableId}/links/{columnId}/rows/{rowId}": {
"parameters": [
{
"schema": {
"type": "string"
},
"name": "tableId",
"in": "path",
"required": true,
"description": "Table ID"
},
{
"schema": {
"type": "string"
},
"name": "viewId",
"in": "query",
"required": true,
"description": "View ID"
}
],
"get": {
"summary": "Get Nested Relations Rows",
"operationId": "db-data-table-row-nested-list",
"description": "Linked rows in a given Links/LinkToAnotherRecord column",
"tags": [
"DB Data Table Row"
],
"parameters": [
{
"schema": {
"type": "array"
},
"in": "query",
"name": "fields",
"description": "Which fields to be shown"
},
{
"schema": {
"oneOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "string"
}
]
},
"in": "query",
"name": "sort",
"description": "The result will be sorted based on `sort` query"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "where",
"description": "Extra filtering"
},
{
"schema": {
"type": "integer",
"minimum": 0
},
"in": "query",
"name": "offset",
"description": "Offset in rows"
},
{
"schema": {
"type": "integer",
"minimum": 1
},
"in": "query",
"name": "limit",
"description": "Limit in rows"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "sortArrJson",
"description": "Used for multiple sort queries"
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "filterArrJson",
"description": "Used for multiple filter queries"
},
{
"$ref": "#/components/parameters/xc-auth"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"list": {
"type": "array",
"description": "List of data objects",
"items": {
"type": "object"
}
},
"pageInfo": {
"$ref": "#/components/schemas/Paginated",
"description": "Paginated Info"
}
},
"required": [
"list",
"pageInfo"
]
},
"examples": {
"Example 1": {
"value": {
"list": [
{
"Id": 1,
"Title": "baz",
"SingleSelect": null,
"Sheet-1 List": [
{
"Id": 1,
"Title": "baz"
}
],
"LTAR": [
{
"Id": 1,
"Title": "baz"
}
]
},
{
"Id": 2,
"Title": "foo",
"SingleSelect": "a",
"Sheet-1 List": [
{
"Id": 2,
"Title": "foo"
}
],
"LTAR": [
{
"Id": 2,
"Title": "foo"
}
]
},
{
"Id": 3,
"Title": "bar",
"SingleSelect": "b",
"Sheet-1 List": [],
"LTAR": []
}
],
"pageInfo": {
"totalRows": 3,
"page": 1,
"pageSize": 25,
"isFirstPage": true,
"isLastPage": true
}
}
}
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
}
}
},
"post": {
"summary": "Create Nested Relations Rows",
"operationId": "db-data-table-row-nested-link",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
}
},
"tags": [
"DB Data Table Row"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"type": "object"
},
{
"type": "array",
"items": {
"type": "object"
}
}
]
},
"examples": {
"Example 1": {
"value": [
{
"Id": 1
}
]
}
}
}
}
},
"description": "Create a link with the row.",
"parameters": [
{
"$ref": "#/components/parameters/xc-auth"
}
]
},
"delete": {
"summary": "Delete Nested Relations Rows",
"operationId": "db-data-table-row-nested-unlink",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
}
},
"tags": [
"DB Data Table Row"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"type": "object"
},
{
"type": "array",
"items": {
"type": "object"
}
}
]
},
"examples": {
"Example 1": {
"value": [
{
"Id": 1
}
]
}
}
}
}
},
"description": "Create a new row in a given table and project.",
"parameters": [
{
"$ref": "#/components/parameters/xc-auth"
}
]
}
}
},
"components": {
@ -20901,7 +21791,7 @@
},
{
"type": "boolean"
},
},
{
"type": "number"
}

18
packages/nocodb/src/services/data-table.service.ts

@ -73,7 +73,11 @@ export class DataTableService {
// if array then do bulk insert
const result = await baseModel.bulkInsert(
Array.isArray(param.body) ? param.body : [param.body],
{ cookie: param.cookie, insertOneByOneAsFallback: true },
{
cookie: param.cookie,
insertOneByOneAsFallback: true,
isSingleRecordInsertion: !Array.isArray(param.body),
},
);
return Array.isArray(param.body) ? result : result[0];
@ -101,7 +105,11 @@ export class DataTableService {
await baseModel.bulkUpdate(
Array.isArray(param.body) ? param.body : [param.body],
{ cookie: param.cookie, throwExceptionIfNotExist: true },
{
cookie: param.cookie,
throwExceptionIfNotExist: true,
isSingleRecordUpdation: !Array.isArray(param.body),
},
);
return this.extractIdObj({ body: param.body, model });
@ -128,7 +136,11 @@ export class DataTableService {
await baseModel.bulkDelete(
Array.isArray(param.body) ? param.body : [param.body],
{ cookie: param.cookie, throwExceptionIfNotExist: true },
{
cookie: param.cookie,
throwExceptionIfNotExist: true,
isSingleRecordDeletion: !Array.isArray(param.body),
},
);
return this.extractIdObj({ body: param.body, model });

19
packages/nocodb/src/utils/common/NcConnectionMgrv2.ts

@ -66,13 +66,22 @@ export default class NcConnectionMgrv2 {
connection: {
...defaultConnectionConfig,
...connectionConfig.connection,
typeCast(_field, next) {
typeCast(field, next) {
const res = next();
if (res instanceof Buffer) {
return [...res]
.map((v) => ('00' + v.toString(16)).slice(-2))
.join('');
// mysql `bit` datatype returns value as Buffer, convert it to integer number
if (field.type == 'BIT' && res && res instanceof Buffer) {
return parseInt(
[...res].map((v) => ('00' + v.toString(16)).slice(-2)).join(''),
16,
);
}
// mysql `decimal` datatype returns value as string, convert it to float number
if (field.type == 'NEWDECIMAL') {
return res && parseFloat(res);
}
return res;
},
},

206
packages/nocodb/tests/unit/rest/tests/newDataApis.test.ts

@ -151,6 +151,7 @@ async function ncAxiosGet({
expect(response.status).to.equal(status);
return response;
}
async function ncAxiosPost({
url = `/api/v1/tables/${table.id}/rows`,
body = {},
@ -163,6 +164,7 @@ async function ncAxiosPost({
expect(response.status).to.equal(status);
return response;
}
async function ncAxiosPatch({
url = `/api/v1/tables/${table.id}/rows`,
body = {},
@ -175,6 +177,7 @@ async function ncAxiosPatch({
expect(response.status).to.equal(status);
return response;
}
async function ncAxiosDelete({
url = `/api/v1/tables/${table.id}/rows`,
body = {},
@ -215,6 +218,7 @@ async function ncAxiosLinkGet({
return response;
}
async function ncAxiosLinkAdd({
urlParams: { tableId, linkId, rowId },
body = {},
@ -241,6 +245,7 @@ async function ncAxiosLinkAdd({
return response;
}
async function ncAxiosLinkRemove({
urlParams: { tableId, linkId, rowId },
body = {},
@ -300,9 +305,8 @@ function generalDb() {
cityColumns = await cityTable.getColumns();
});
it('Nested List - Link to another record', async function () {
it.only('Nested List - Link to another record', async function () {
const expectedRecords = [1, 3, 1, 2];
const expectedRecordsPg = ['1', '3', '1', '2'];
// read first 4 records
const records = await ncAxiosGet({
@ -315,11 +319,10 @@ function generalDb() {
// extract LTAR column "City List"
const cityList = records.body.list.map((r) => r['Cities']);
if (isPg(context)) expect(cityList).to.deep.equal(expectedRecordsPg);
else expect(cityList).to.deep.equal(expectedRecords);
expect(cityList).to.deep.equal(expectedRecords);
});
it('Nested List - Lookup', async function () {
it.only('Nested List - Lookup', async function () {
const lookupColumn = await createLookupColumn(context, {
project: sakilaProject,
title: 'Lookup',
@ -349,7 +352,7 @@ function generalDb() {
expect(lookupData).to.deep.equal(expectedRecords);
});
it('Nested List - Rollup', async function () {
it.only('Nested List - Rollup', async function () {
const rollupColumn = await createRollupColumn(context, {
project: sakilaProject,
title: 'Rollup',
@ -360,7 +363,6 @@ function generalDb() {
});
const expectedRecords = [1, 3, 1, 2];
const expectedRecordsPg = ['1', '3', '1', '2'];
// read first 4 records
const records = await ncAxiosGet({
@ -373,14 +375,11 @@ function generalDb() {
// extract Lookup column
const rollupData = records.body.list.map((record) => record.Rollup);
if (isPg(context)) {
expect(rollupData).to.deep.equal(expectedRecordsPg);
} else {
expect(rollupData).to.deep.equal(expectedRecords);
}
expect(rollupData).to.deep.equal(expectedRecords);
});
it('Nested Read - Link to another record', async function () {
it.only('Nested Read - Link to another record', async function () {
const records = await ncAxiosGet({
url: `/api/v1/tables/${countryTable.id}/rows/1`,
});
@ -389,7 +388,7 @@ function generalDb() {
expect(+records.body['Cities']).to.equal(1);
});
it('Nested Read - Lookup', async function () {
it.only('Nested Read - Lookup', async function () {
const lookupColumn = await createLookupColumn(context, {
project: sakilaProject,
title: 'Lookup',
@ -404,7 +403,7 @@ function generalDb() {
expect(records.body.Lookup).to.deep.equal(['Kabul']);
});
it('Nested Read - Rollup', async function () {
it.only('Nested Read - Rollup', async function () {
const rollupColumn = await createRollupColumn(context, {
project: sakilaProject,
title: 'Rollup',
@ -417,11 +416,8 @@ function generalDb() {
const records = await ncAxiosGet({
url: `/api/v1/tables/${countryTable.id}/rows/1`,
});
if (isPg(context)) {
expect(records.body.Rollup).to.equal('1');
} else {
expect(records.body.Rollup).to.equal(1);
}
expect(records.body.Rollup).to.equal(1);
});
}
@ -474,7 +470,7 @@ function textBased() {
/////////////////////////////////////////////////////////////////////////////
it('List: default', async function () {
it.only('List: default', async function () {
const rsp = await ncAxiosGet();
const expectedPageInfo = {
@ -496,7 +492,7 @@ function textBased() {
);
});
it('List: offset, limit', async function () {
it.only('List: offset, limit', async function () {
const rsp = await ncAxiosGet({ query: { offset: 200, limit: 100 } });
const expectedPageInfo = {
@ -509,7 +505,7 @@ function textBased() {
expect(rsp.body.pageInfo).to.deep.equal(expectedPageInfo);
});
it('List: fields, single', async function () {
it.only('List: fields, single', async function () {
const rsp = await ncAxiosGet({
query: { fields: 'SingleLineText' },
});
@ -519,7 +515,7 @@ function textBased() {
).to.equal(true);
});
it('List: fields, multiple', async function () {
it.only('List: fields, multiple', async function () {
const rsp = await ncAxiosGet({
query: { fields: ['SingleLineText', 'MultiLineText'] },
});
@ -532,7 +528,7 @@ function textBased() {
).to.equal(true);
});
it('List: sort, ascending', async function () {
it.only('List: sort, ascending', async function () {
const sortColumn = columns.find((c) => c.title === 'SingleLineText');
const rsp = await ncAxiosGet({
query: { sort: 'SingleLineText', limit: 400 },
@ -543,7 +539,7 @@ function textBased() {
expect(sortedArray).to.deep.equal(sortedArray.sort());
});
it('List: sort, descending', async function () {
it.only('List: sort, descending', async function () {
const sortColumn = columns.find((c) => c.title === 'SingleLineText');
const rsp = await ncAxiosGet({
query: { sort: '-SingleLineText', limit: 400 },
@ -554,7 +550,7 @@ function textBased() {
expect(descSortedArray).to.deep.equal(descSortedArray.sort().reverse());
});
it('List: sort, multiple', async function () {
it.only('List: sort, multiple', async function () {
const rsp = await ncAxiosGet({
query: {
sort: ['-SingleLineText', '-MultiLineText'],
@ -570,7 +566,7 @@ function textBased() {
expect(sortedArray).to.deep.equal(sortedArray.sort().reverse());
});
it('List: filter, single', async function () {
it.only('List: filter, single', async function () {
const rsp = await ncAxiosGet({
query: {
where: '(SingleLineText,eq,Afghanistan)',
@ -583,7 +579,7 @@ function textBased() {
expect(filteredArray).to.deep.equal(filteredArray.fill('Afghanistan'));
});
it('List: filter, multiple', async function () {
it.only('List: filter, multiple', async function () {
const rsp = await ncAxiosGet({
query: {
where:
@ -601,7 +597,7 @@ function textBased() {
);
});
it('List: view ID', async function () {
it.only('List: view ID', async function () {
const gridView = await createView(context, {
title: 'grid0',
table,
@ -735,7 +731,7 @@ function textBased() {
return gridView;
}
it('List: view ID + sort', async function () {
it.only('List: view ID + sort', async function () {
const gridView = await prepareViewForTests();
const rsp = await ncAxiosGet({
@ -754,7 +750,7 @@ function textBased() {
expect(sortedArray).to.deep.equal(sortedArray.sort());
});
it('List: view ID + filter', async function () {
it.only('List: view ID + filter', async function () {
const gridView = await prepareViewForTests();
const rsp = await ncAxiosGet({
@ -773,7 +769,7 @@ function textBased() {
expect(filteredArray).to.deep.equal(filteredArray.fill('1-541-754-3010'));
});
it('List: view ID + fields', async function () {
it.only('List: view ID + fields', async function () {
const gridView = await prepareViewForTests();
const rsp = await ncAxiosGet({
@ -793,7 +789,7 @@ function textBased() {
});
// Error handling
it('List: invalid ID', async function () {
it.only('List: invalid ID', async function () {
// Invalid table ID
await ncAxiosGet({
url: `/api/v1/tables/123456789/rows`,
@ -809,7 +805,7 @@ function textBased() {
});
});
it('List: invalid limit & offset', async function () {
it.only('List: invalid limit & offset', async function () {
const expectedPageInfo = {
totalRows: 400,
page: 1,
@ -862,7 +858,7 @@ function textBased() {
expect(rsp.body.list.length).to.equal(0);
});
it('List: invalid sort, filter, fields', async function () {
it.only('List: invalid sort, filter, fields', async function () {
// expect to ignore invalid sort, filter, fields
await ncAxiosGet({
@ -896,13 +892,13 @@ function textBased() {
Phone: '1-234-567-8910',
};
it('Create: all fields', async function () {
it.only('Create: all fields', async function () {
const rsp = await ncAxiosPost({ body: newRecord });
expect(rsp.body).to.deep.equal({ Id: 401 });
});
it('Create: few fields left out', async function () {
it.only('Create: few fields left out', async function () {
const newRecord = {
SingleLineText: 'abc',
MultiLineText: 'abc abc \n abc \r abc \t abc 1234!@#$%^&*()_+',
@ -913,14 +909,14 @@ function textBased() {
expect(rsp.body).to.deep.equal({ Id: 401 });
});
it('Create: bulk', async function () {
it.only('Create: bulk', async function () {
const rsp = await ncAxiosPost({ body: [newRecord, newRecord, newRecord] });
expect(rsp.body).to.deep.equal([{ Id: 401 }, { Id: 402 }, { Id: 403 }]);
});
// Error handling
it('Create: invalid ID', async function () {
it.only('Create: invalid ID', async function () {
// Invalid table ID
await ncAxiosPost({
url: `/api/v1/tables/123456789/rows`,
@ -948,13 +944,13 @@ function textBased() {
/////////////////////////////////////////////////////////////////////////////
it('Read: all fields', async function () {
it.only('Read: all fields', async function () {
const rsp = await ncAxiosGet({
url: `/api/v1/tables/${table.id}/rows/100`,
});
});
it('Read: invalid ID', async function () {
it.only('Read: invalid ID', async function () {
// Invalid table ID
await ncAxiosGet({
url: `/api/v1/tables/123456789/rows/100`,
@ -974,7 +970,7 @@ function textBased() {
/////////////////////////////////////////////////////////////////////////////
it('Update: all fields', async function () {
it.only('Update: all fields', async function () {
const rsp = await ncAxiosPatch({
body: [
{
@ -986,7 +982,7 @@ function textBased() {
expect(rsp.body).to.deep.equal([{ Id: 1 }]);
});
it('Update: partial', async function () {
it.only('Update: partial', async function () {
const recordBeforeUpdate = await ncAxiosGet({
url: `/api/v1/tables/${table.id}/rows/1`,
});
@ -1012,7 +1008,7 @@ function textBased() {
});
});
it('Update: bulk', async function () {
it.only('Update: bulk', async function () {
const rsp = await ncAxiosPatch({
body: [
{
@ -1032,7 +1028,7 @@ function textBased() {
// Error handling
it('Update: invalid ID', async function () {
it.only('Update: invalid ID', async function () {
// Invalid table ID
await ncAxiosPatch({
url: `/api/v1/tables/123456789/rows`,
@ -1053,7 +1049,7 @@ function textBased() {
/////////////////////////////////////////////////////////////////////////////
it('Delete: single', async function () {
it.only('Delete: single', async function () {
const rsp = await ncAxiosDelete({ body: [{ Id: 1 }] });
expect(rsp.body).to.deep.equal([{ Id: 1 }]);
@ -1064,7 +1060,7 @@ function textBased() {
});
});
it('Delete: bulk', async function () {
it.only('Delete: bulk', async function () {
const rsp = await ncAxiosDelete({ body: [{ Id: 1 }, { Id: 2 }] });
expect(rsp.body).to.deep.equal([{ Id: 1 }, { Id: 2 }]);
@ -1081,7 +1077,7 @@ function textBased() {
// Error handling
it('Delete: invalid ID', async function () {
it.only('Delete: invalid ID', async function () {
// Invalid table ID
await ncAxiosDelete({
url: `/api/v1/tables/123456789/rows`,
@ -1231,97 +1227,97 @@ function numberBased() {
const recordsPg = [
{
Id: 1,
Number: '33',
Decimal: '33.3',
Currency: '33.3',
Number: 33,
Decimal: 33.3,
Currency: 33.3,
Percent: 33,
Duration: '10',
Duration: 10,
Rating: 0,
},
{
Id: 2,
Number: null,
Decimal: '456.34',
Currency: '456.34',
Decimal: 456.34,
Currency: 456.34,
Percent: null,
Duration: '20',
Duration: 20,
Rating: 1,
},
{
Id: 3,
Number: '456',
Decimal: '333.3',
Currency: '333.3',
Number: 456,
Decimal: 333.3,
Currency: 333.3,
Percent: 456,
Duration: '30',
Duration: 30,
Rating: 2,
},
{
Id: 4,
Number: '333',
Number: 333,
Decimal: null,
Currency: null,
Percent: 333,
Duration: '40',
Duration: 40,
Rating: 3,
},
{
Id: 5,
Number: '267',
Decimal: '267.5674',
Currency: '267.5674',
Number: 267,
Decimal: 267.5674,
Currency: 267.5674,
Percent: 267,
Duration: '50',
Duration: 50,
Rating: null,
},
{
Id: 6,
Number: '34',
Decimal: '34',
Currency: '34',
Number: 34,
Decimal: 34,
Currency: 34,
Percent: 34,
Duration: '60',
Duration: 60,
Rating: 0,
},
{
Id: 7,
Number: '8754',
Decimal: '8754',
Currency: '8754',
Number: 8754,
Decimal: 8754,
Currency: 8754,
Percent: 8754,
Duration: null,
Rating: 4,
},
{
Id: 8,
Number: '3234',
Decimal: '3234.547',
Currency: '3234.547',
Number: 3234,
Decimal: 3234.547,
Currency: 3234.547,
Percent: 3234,
Duration: '70',
Duration: 70,
Rating: 5,
},
{
Id: 9,
Number: '44',
Decimal: '44.2647',
Currency: '44.2647',
Number: 44,
Decimal: 44.2647,
Currency: 44.2647,
Percent: 44,
Duration: '80',
Duration: 80,
Rating: 0,
},
{
Id: 10,
Number: '33',
Decimal: '33.98',
Currency: '33.98',
Number: 33,
Decimal: 33.98,
Currency: 33.98,
Percent: 33,
Duration: '90',
Duration: 90,
Rating: 1,
},
];
it('Number based- List & CRUD', async function () {
it.only('Number based- List & CRUD', async function () {
// list 10 records
let rsp = await ncAxiosGet({
query: {
@ -1382,11 +1378,11 @@ function numberBased() {
Rating: 5,
};
const updatedRecordPg = {
Number: '55',
Decimal: '55.5',
Currency: '55.5',
Number: 55,
Decimal: 55.5,
Currency: 55.5,
Percent: 55,
Duration: '55',
Duration: 55,
Rating: 5,
};
@ -1550,7 +1546,7 @@ function selectBased() {
},
];
it('Select based- List & CRUD', async function () {
it.only('Select based- List & CRUD', async function () {
// list 10 records
let rsp = await ncAxiosGet({
query: {
@ -1683,7 +1679,7 @@ function dateBased() {
expect(insertedRecords.length).to.equal(800);
});
it('Date based- List & CRUD', async function () {
it.only('Date based- List & CRUD', async function () {
// list 10 records
let rsp = await ncAxiosGet({
query: {
@ -1913,7 +1909,7 @@ function linkBased() {
return columns.find((c) => c.title === title).id;
}
it('Has-Many ', async function () {
it.only('Has-Many ', async function () {
// Create hm link between Country and City
await ncAxiosLinkAdd({
urlParams: {
@ -2074,7 +2070,7 @@ function linkBased() {
return Array.from({ length: count }, (_, index) => i + index);
}
it('Create Many-Many ', async function () {
it.only('Create Many-Many ', async function () {
await ncAxiosLinkAdd({
urlParams: {
tableId: tblActor.id,
@ -2248,7 +2244,7 @@ function linkBased() {
// Other scenarios
// Has-many : change an existing link to a new one
it('HM: Change an existing link to a new one', async function () {
it.only('HM: Change an existing link to a new one', async function () {
// add a link
await ncAxiosLinkAdd({
urlParams: {
@ -2295,7 +2291,7 @@ function linkBased() {
});
// limit & offset verification
it('Limit & offset verification', async function () {
it.only('Limit & offset verification', async function () {
// add a link
await ncAxiosLinkAdd({
urlParams: {
@ -2603,7 +2599,7 @@ function linkBased() {
}
// Error handling (has-many)
it('Error handling : HM: Nested ADD', async function () {
it.only('Error handling : HM: Nested ADD', async function () {
const validParams = {
urlParams: {
tableId: tblCountry.id,
@ -2617,7 +2613,7 @@ function linkBased() {
await nestedAddTests(validParams);
});
it('Error handling : HM: Nested REMOVE', async function () {
it.only('Error handling : HM: Nested REMOVE', async function () {
// Prepare data
await ncAxiosLinkAdd({
urlParams: {
@ -2642,7 +2638,7 @@ function linkBased() {
await nestedRemoveTests(validParams);
});
it('Error handling : HM: Nested List', async function () {
it.only('Error handling : HM: Nested List', async function () {
// Prepare data
await ncAxiosLinkAdd({
urlParams: {
@ -2671,7 +2667,7 @@ function linkBased() {
});
// Error handling (belongs to)
it('Error handling : BT: Nested ADD', async function () {
it.only('Error handling : BT: Nested ADD', async function () {
const validParams = {
urlParams: {
tableId: tblCity.id,
@ -2685,7 +2681,7 @@ function linkBased() {
await nestedAddTests(validParams, 'bt');
});
it('Error handling : BT: Nested REMOVE', async function () {
it.only('Error handling : BT: Nested REMOVE', async function () {
// Prepare data
await ncAxiosLinkAdd({
urlParams: {
@ -2710,7 +2706,7 @@ function linkBased() {
await nestedRemoveTests(validParams, 'bt');
});
it('Error handling : BT: Nested List', async function () {
it.only('Error handling : BT: Nested List', async function () {
// Prepare data
await ncAxiosLinkAdd({
urlParams: {
@ -2739,7 +2735,7 @@ function linkBased() {
});
// Error handling (many-many)
it('Error handling : MM: Nested ADD', async function () {
it.only('Error handling : MM: Nested ADD', async function () {
const validParams = {
urlParams: {
tableId: tblActor.id,
@ -2753,7 +2749,7 @@ function linkBased() {
await nestedAddTests(validParams);
});
it('Error handling : MM: Nested REMOVE', async function () {
it.only('Error handling : MM: Nested REMOVE', async function () {
// Prepare data
await ncAxiosLinkAdd({
urlParams: {
@ -2778,7 +2774,7 @@ function linkBased() {
await nestedRemoveTests(validParams);
});
it('Error handling : MM: Nested List', async function () {
it.only('Error handling : MM: Nested List', async function () {
// Prepare data
await ncAxiosLinkAdd({
urlParams: {

6
packages/nocodb/tests/unit/rest/tests/tableRow.test.ts

@ -301,11 +301,7 @@ function tableStaticTest() {
.expect(200);
const record = response.body;
if (isPg(context)) {
expect(record['Films']).to.equal('19');
} else {
expect(record['Films']).to.equal(19);
}
expect(record['Films']).to.equal(19);
});
it('Exist should be true table row when it exists', async function () {
const row = await getOneRow(context, {

10
tests/playwright/tests/db/features/verticalFillHandle.spec.ts

@ -99,12 +99,12 @@ test.describe('Fill Handle', () => {
test('Number based', async () => {
const fields = [
{ title: 'Number', value: '33', type: 'text' },
{ title: 'Decimal', value: '33.3', type: 'text' },
{ title: 'Currency', value: '33.30', type: 'text' },
{ title: 'Percent', value: '33', type: 'text' },
{ title: 'Number', value: 33, type: 'text' },
{ title: 'Decimal', value: 33.3, type: 'text' },
{ title: 'Currency', value: 33.3, type: 'text' },
{ title: 'Percent', value: 33, type: 'text' },
{ title: 'Duration', value: '00:01', type: 'text' },
{ title: 'Rating', value: '3', type: 'rating' },
{ title: 'Rating', value: 3, type: 'rating' },
{ title: 'Year', value: '2023', type: 'year' },
{ title: 'Time', value: '02:02', type: 'time' },
];

16
tests/playwright/tests/db/features/webhook.spec.ts

@ -694,22 +694,22 @@ test.describe.serial('Webhook', () => {
{
Id: 1,
Country: 'India',
CountryCode: '1',
CityList: '2',
CityCodeRollup: '2',
CountryCode: 1,
CityList: 2,
CityCodeRollup: 2,
CityCodeFormula: 100,
CityCodeLookup: ['23', '33'],
CityCodeLookup: [23, 33],
},
],
rows: [
{
Id: 1,
Country: 'INDIA',
CountryCode: '1',
CityList: '2',
CityCodeRollup: '2',
CountryCode: 1,
CityList: 2,
CityCodeRollup: 2,
CityCodeFormula: 100,
CityCodeLookup: ['23', '33'],
CityCodeLookup: [23, 33],
},
],
},

4
tests/playwright/tests/utils/general.ts

@ -37,10 +37,8 @@ function isSubset(obj, potentialSubset) {
if (!isSubset(objValue, potentialValue)) {
return false;
}
// skip strict type check since different database returns number values in string/number type
// todo: revert back to strict type check once we are consistent with type
// eslint-disable-next-line no-prototype-builtins
} else if (!obj.hasOwnProperty(prop) || objValue != potentialValue) {
} else if (!obj.hasOwnProperty(prop) || objValue !== potentialValue) {
return false;
}
}

Loading…
Cancel
Save