From a64fb105e314632a5cccf6d5a4ebc6d3a72ccbbe Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Thu, 7 Oct 2021 23:02:51 +0200 Subject: [PATCH] (core) Use GristObjCode in CellValue Summary: Makes type checking a bit stronger Test Plan: it just has to compile Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D3065 --- app/client/models/DataTableModelWithDiff.ts | 2 +- app/common/gristTypes.ts | 28 +++++---------------- app/common/schema.ts | 7 ++++-- app/plugin/GristData.ts | 18 ++++++++++++- app/plugin/objtypes.ts | 20 +++++++-------- app/server/lib/DocStorage.ts | 3 ++- app/server/lib/ExpandedQuery.ts | 17 +++++++------ app/server/lib/GranularAccess.ts | 5 ++-- sandbox/gen_js_schema.py | 7 ++++-- 9 files changed, 58 insertions(+), 49 deletions(-) diff --git a/app/client/models/DataTableModelWithDiff.ts b/app/client/models/DataTableModelWithDiff.ts index 5c9efa94..a867dba2 100644 --- a/app/client/models/DataTableModelWithDiff.ts +++ b/app/client/models/DataTableModelWithDiff.ts @@ -8,7 +8,7 @@ import { TableData } from 'app/client/models/TableData'; import { createEmptyTableDelta, getTableIdAfter, getTableIdBefore, TableDelta } from 'app/common/ActionSummary'; import { DisposableWithEvents } from 'app/common/DisposableWithEvents'; import { CellVersions, UserAction } from 'app/common/DocActions'; -import { GristObjCode } from "app/common/gristTypes"; +import { GristObjCode } from 'app/plugin/GristData'; import { CellDelta } from 'app/common/TabularDiff'; import { DocStateComparisonDetails } from 'app/common/UserAPI'; import { CellValue } from 'app/plugin/GristData'; diff --git a/app/common/gristTypes.ts b/app/common/gristTypes.ts index dab8123a..8c2507e5 100644 --- a/app/common/gristTypes.ts +++ b/app/common/gristTypes.ts @@ -1,6 +1,7 @@ import { CellValue, CellVersions } from 'app/common/DocActions'; +import { GristObjCode } from 'app/plugin/GristData'; import isString = require('lodash/isString'); -import {removePrefix} from "./gutil"; +import { removePrefix } from "./gutil"; // tslint:disable:object-literal-key-quotes @@ -14,23 +15,6 @@ export type GristTypeInfo = {type: 'RefList', tableId: string} | {type: Exclude}; - -// Letter codes for CellValue types encoded as [code, args...] tuples. -export const enum GristObjCode { - List = 'L', - Dict = 'O', - DateTime = 'D', - Date = 'd', - Skip = 'S', - Censored = 'C', - Reference = 'R', - ReferenceList = 'r', - Exception = 'E', - Pending = 'P', - Unmarshallable = 'U', - Versions = 'V', -} - export const MANUALSORT = 'manualSort'; // Whether a column is internal and should be hidden. @@ -93,9 +77,9 @@ export function extractInfoFromColType(colType: string): GristTypeInfo { export function reencodeAsAny(value: CellValue, typeInfo: GristTypeInfo): CellValue { if (typeof value === 'number') { switch (typeInfo.type) { - case 'Date': return ['d', value]; - case 'DateTime': return ['D', value, typeInfo.timezone]; - case 'Ref': return ['R', typeInfo.tableId, value]; + case 'Date': return [GristObjCode.Date, value]; + case 'DateTime': return [GristObjCode.DateTime, value, typeInfo.timezone]; + case 'Ref': return [GristObjCode.Reference, typeInfo.tableId, value]; } } return value; @@ -105,7 +89,7 @@ export function reencodeAsAny(value: CellValue, typeInfo: GristTypeInfo): CellVa /** * Returns whether a value (as received in a DocAction) represents a custom object. */ -export function isObject(value: CellValue): value is [string, any?] { +export function isObject(value: CellValue): value is [GristObjCode, any?] { return Array.isArray(value); } diff --git a/app/common/schema.ts b/app/common/schema.ts index db762303..66669e4f 100644 --- a/app/common/schema.ts +++ b/app/common/schema.ts @@ -1,4 +1,7 @@ /*** THIS FILE IS AUTO-GENERATED BY core/sandbox/gen_js_schema.py ***/ + +import { GristObjCode } from "app/plugin/GristData"; + // tslint:disable:object-literal-key-quotes export const SCHEMA_VERSION = 24; @@ -216,7 +219,7 @@ export interface SchemaTypes { displayCol: number; visibleCol: number; recalcWhen: number; - recalcDeps: ['L', ...number[]]|null; + recalcDeps: [GristObjCode.List, ...number[]]|null; }; "_grist_Imports": { @@ -328,7 +331,7 @@ export interface SchemaTypes { "_grist_Triggers": { tableRef: number; - eventTypes: ['L', ...string[]]|null; + eventTypes: [GristObjCode.List, ...string[]]|null; isReadyColRef: number; actions: string; }; diff --git a/app/plugin/GristData.ts b/app/plugin/GristData.ts index f3f21dde..99362d65 100644 --- a/app/plugin/GristData.ts +++ b/app/plugin/GristData.ts @@ -1,4 +1,20 @@ -export type CellValue = number|string|boolean|null|[string, ...unknown[]]; +// Letter codes for CellValue types encoded as [code, args...] tuples. +export const enum GristObjCode { + List = 'L', + Dict = 'O', + DateTime = 'D', + Date = 'd', + Skip = 'S', + Censored = 'C', + Reference = 'R', + ReferenceList = 'r', + Exception = 'E', + Pending = 'P', + Unmarshallable = 'U', + Versions = 'V', +} + +export type CellValue = number|string|boolean|null|[GristObjCode, ...unknown[]]; export interface RowRecord { id: number; diff --git a/app/plugin/objtypes.ts b/app/plugin/objtypes.ts index dd9c1710..40e651cb 100644 --- a/app/plugin/objtypes.ts +++ b/app/plugin/objtypes.ts @@ -4,7 +4,7 @@ */ // tslint:disable:max-classes-per-file -import {CellValue} from 'app/plugin/GristData'; +import { CellValue, GristObjCode } from 'app/plugin/GristData'; import isPlainObject = require('lodash/isPlainObject'); // The text to show on cells whose values are pending. @@ -165,32 +165,32 @@ export function encodeObject(value: unknown): CellValue { if (value == null) { return null; } else if (value instanceof Reference) { - return ['R', value.tableId, value.rowId]; + return [GristObjCode.Reference, value.tableId, value.rowId]; } else if (value instanceof ReferenceList) { - return ['r', value.tableId, value.rowIds]; + return [GristObjCode.ReferenceList, value.tableId, value.rowIds]; } else if (value instanceof Date) { const timestamp = value.valueOf() / 1000; if ('timezone' in value) { - return ['D', timestamp, (value as GristDateTime).timezone]; + return [GristObjCode.DateTime, timestamp, (value as GristDateTime).timezone]; } else { // TODO Depending on how it's used, may want to return ['d', timestamp] for UTC midnight. - return ['D', timestamp, 'UTC']; + return [GristObjCode.DateTime, timestamp, 'UTC']; } } else if (value instanceof CensoredValue) { - return ['C']; + return [GristObjCode.Censored]; } else if (value instanceof RaisedException) { - return ['E', value.name, value.message, value.details]; + return [GristObjCode.Exception, value.name, value.message, value.details]; } else if (Array.isArray(value)) { - return ['L', ...value.map(encodeObject)]; + return [GristObjCode.List, ...value.map(encodeObject)]; } else if (isPlainObject(value)) { - return ['O', mapValues(value as any, encodeObject, {sort: true})]; + return [GristObjCode.Dict, mapValues(value as any, encodeObject, {sort: true})]; } } catch (e) { // Fall through to return a best-effort representation. } // We either don't know how to convert the value, or failed during the conversion. Instead we // return an "UnmarshallableValue" object, with repr() of the value to show to the user. - return ['U', UnknownValue.safeRepr(value)]; + return [GristObjCode.Unmarshallable, UnknownValue.safeRepr(value)]; } diff --git a/app/server/lib/DocStorage.ts b/app/server/lib/DocStorage.ts index 54bb10b1..09fdfdc6 100644 --- a/app/server/lib/DocStorage.ts +++ b/app/server/lib/DocStorage.ts @@ -13,6 +13,7 @@ import {BulkColValues, DocAction, TableColValues, TableDataAction, toTableDataAc import * as gristTypes from 'app/common/gristTypes'; import * as marshal from 'app/common/marshal'; import * as schema from 'app/common/schema'; +import {GristObjCode} from "app/plugin/GristData"; import {ActionHistoryImpl} from 'app/server/lib/ActionHistoryImpl'; import {ExpandedQuery} from 'app/server/lib/ExpandedQuery'; import {IDocStorageManager} from 'app/server/lib/IDocStorageManager'; @@ -35,7 +36,7 @@ const debuglog = util.debuglog('db'); const maxSQLiteVariables = 500; // Actually could be 999, so this is playing it safe. -const PENDING_VALUE = [gristTypes.GristObjCode.Pending]; +const PENDING_VALUE = [GristObjCode.Pending]; export class DocStorage implements ISQLiteDB { diff --git a/app/server/lib/ExpandedQuery.ts b/app/server/lib/ExpandedQuery.ts index 55b8f366..24d497ae 100644 --- a/app/server/lib/ExpandedQuery.ts +++ b/app/server/lib/ExpandedQuery.ts @@ -1,9 +1,10 @@ -import {ServerQuery} from 'app/common/ActiveDocAPI'; -import {ApiError} from 'app/common/ApiError'; -import {DocData} from 'app/common/DocData'; -import {parseFormula} from 'app/common/Formula'; -import {removePrefix} from 'app/common/gutil'; -import {quoteIdent} from 'app/server/lib/SQLiteDB'; +import { ServerQuery } from 'app/common/ActiveDocAPI'; +import { ApiError } from 'app/common/ApiError'; +import { DocData } from 'app/common/DocData'; +import { parseFormula } from 'app/common/Formula'; +import { removePrefix } from 'app/common/gutil'; +import { GristObjCode } from 'app/plugin/GristData'; +import { quoteIdent } from 'app/server/lib/SQLiteDB'; /** * Represents a query for Grist data with support for SQL-based @@ -19,7 +20,7 @@ export interface ExpandedQuery extends ServerQuery { // step. That means we need to pass the error message along // explicitly. constants?: { - [colId: string]: ['E', string] | ['P']; + [colId: string]: [GristObjCode.Exception, string] | [GristObjCode.Pending]; }; // A list of join clauses to bring in data from other tables. @@ -115,7 +116,7 @@ export function expandQuery(iquery: ServerQuery, docData: DocData, onDemandFormu // We add a trivial selection, and store errors in the query for substitution later. sqlFormula = '0'; if (!query.constants) { query.constants = {}; } - query.constants[colId] = ['E', error]; + query.constants[colId] = [GristObjCode.Exception, error]; } if (sqlFormula) { selects.add(`${sqlFormula} as ${quoteIdent(colId)}`); diff --git a/app/server/lib/GranularAccess.ts b/app/server/lib/GranularAccess.ts index 9e0c31c7..a19b23c5 100644 --- a/app/server/lib/GranularAccess.ts +++ b/app/server/lib/GranularAccess.ts @@ -19,6 +19,7 @@ import { getSetMapValue, isObject, pruneArray } from 'app/common/gutil'; import { canEdit, canView, isValidRole, Role } from 'app/common/roles'; import { FullUser, UserAccessData } from 'app/common/UserAPI'; import { HomeDBManager } from 'app/gen-server/lib/HomeDBManager'; +import { GristObjCode } from 'app/plugin/GristData'; import { compileAclFormula } from 'app/server/lib/ACLFormula'; import { DocClients } from 'app/server/lib/DocClients'; import { getDocSessionAccess, getDocSessionUser, OptDocSession } from 'app/server/lib/DocSession'; @@ -1104,9 +1105,9 @@ export class GranularAccess implements GranularAccessForBundle { if (colValues === undefined) { censorAt = () => 1; } else if (Array.isArray(action[2])) { - censorAt = (colId, idx) => (copyOnNeed() as BulkColValues)[colId][idx] = ['C']; // censored + censorAt = (colId, idx) => (copyOnNeed() as BulkColValues)[colId][idx] = [GristObjCode.Censored]; } else { - censorAt = (colId) => (copyOnNeed() as ColValues)[colId] = ['C']; // censored + censorAt = (colId) => (copyOnNeed() as ColValues)[colId] = [GristObjCode.Censored]; } // These map an index of a row in the action to its index in rowsBefore and in rowsAfter. diff --git a/sandbox/gen_js_schema.py b/sandbox/gen_js_schema.py index 6f72b4a2..0c4547b7 100644 --- a/sandbox/gen_js_schema.py +++ b/sandbox/gen_js_schema.py @@ -12,8 +12,8 @@ _ts_types = { "Int": "number", "PositionNumber": "number", "Ref": "number", - "RefList": "['L', ...number[]]|null", # Non-primitive values are encoded - "ChoiceList": "['L', ...string[]]|null", + "RefList": "[GristObjCode.List, ...number[]]|null", # Non-primitive values are encoded + "ChoiceList": "[GristObjCode.List, ...string[]]|null", "Text": "string", } @@ -24,6 +24,9 @@ def get_ts_type(col_type): def main(): print("""\ /*** THIS FILE IS AUTO-GENERATED BY %s ***/ + +import { GristObjCode } from "app/plugin/GristData"; + // tslint:disable:object-literal-key-quotes export const SCHEMA_VERSION = %d;