(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
pull/115/head
Alex Hall 3 years ago
parent 62db263d1f
commit a64fb105e3

@ -8,7 +8,7 @@ import { TableData } from 'app/client/models/TableData';
import { createEmptyTableDelta, getTableIdAfter, getTableIdBefore, TableDelta } from 'app/common/ActionSummary'; import { createEmptyTableDelta, getTableIdAfter, getTableIdBefore, TableDelta } from 'app/common/ActionSummary';
import { DisposableWithEvents } from 'app/common/DisposableWithEvents'; import { DisposableWithEvents } from 'app/common/DisposableWithEvents';
import { CellVersions, UserAction } from 'app/common/DocActions'; 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 { CellDelta } from 'app/common/TabularDiff';
import { DocStateComparisonDetails } from 'app/common/UserAPI'; import { DocStateComparisonDetails } from 'app/common/UserAPI';
import { CellValue } from 'app/plugin/GristData'; import { CellValue } from 'app/plugin/GristData';

@ -1,6 +1,7 @@
import { CellValue, CellVersions } from 'app/common/DocActions'; import { CellValue, CellVersions } from 'app/common/DocActions';
import { GristObjCode } from 'app/plugin/GristData';
import isString = require('lodash/isString'); import isString = require('lodash/isString');
import {removePrefix} from "./gutil"; import { removePrefix } from "./gutil";
// tslint:disable:object-literal-key-quotes // tslint:disable:object-literal-key-quotes
@ -14,23 +15,6 @@ export type GristTypeInfo =
{type: 'RefList', tableId: string} | {type: 'RefList', tableId: string} |
{type: Exclude<GristType, 'DateTime'|'Ref'|'RefList'>}; {type: Exclude<GristType, 'DateTime'|'Ref'|'RefList'>};
// 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'; export const MANUALSORT = 'manualSort';
// Whether a column is internal and should be hidden. // 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 { export function reencodeAsAny(value: CellValue, typeInfo: GristTypeInfo): CellValue {
if (typeof value === 'number') { if (typeof value === 'number') {
switch (typeInfo.type) { switch (typeInfo.type) {
case 'Date': return ['d', value]; case 'Date': return [GristObjCode.Date, value];
case 'DateTime': return ['D', value, typeInfo.timezone]; case 'DateTime': return [GristObjCode.DateTime, value, typeInfo.timezone];
case 'Ref': return ['R', typeInfo.tableId, value]; case 'Ref': return [GristObjCode.Reference, typeInfo.tableId, value];
} }
} }
return 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. * 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); return Array.isArray(value);
} }

@ -1,4 +1,7 @@
/*** THIS FILE IS AUTO-GENERATED BY core/sandbox/gen_js_schema.py ***/ /*** THIS FILE IS AUTO-GENERATED BY core/sandbox/gen_js_schema.py ***/
import { GristObjCode } from "app/plugin/GristData";
// tslint:disable:object-literal-key-quotes // tslint:disable:object-literal-key-quotes
export const SCHEMA_VERSION = 24; export const SCHEMA_VERSION = 24;
@ -216,7 +219,7 @@ export interface SchemaTypes {
displayCol: number; displayCol: number;
visibleCol: number; visibleCol: number;
recalcWhen: number; recalcWhen: number;
recalcDeps: ['L', ...number[]]|null; recalcDeps: [GristObjCode.List, ...number[]]|null;
}; };
"_grist_Imports": { "_grist_Imports": {
@ -328,7 +331,7 @@ export interface SchemaTypes {
"_grist_Triggers": { "_grist_Triggers": {
tableRef: number; tableRef: number;
eventTypes: ['L', ...string[]]|null; eventTypes: [GristObjCode.List, ...string[]]|null;
isReadyColRef: number; isReadyColRef: number;
actions: string; actions: string;
}; };

@ -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 { export interface RowRecord {
id: number; id: number;

@ -4,7 +4,7 @@
*/ */
// tslint:disable:max-classes-per-file // tslint:disable:max-classes-per-file
import {CellValue} from 'app/plugin/GristData'; import { CellValue, GristObjCode } from 'app/plugin/GristData';
import isPlainObject = require('lodash/isPlainObject'); import isPlainObject = require('lodash/isPlainObject');
// The text to show on cells whose values are pending. // The text to show on cells whose values are pending.
@ -165,32 +165,32 @@ export function encodeObject(value: unknown): CellValue {
if (value == null) { if (value == null) {
return null; return null;
} else if (value instanceof Reference) { } else if (value instanceof Reference) {
return ['R', value.tableId, value.rowId]; return [GristObjCode.Reference, value.tableId, value.rowId];
} else if (value instanceof ReferenceList) { } else if (value instanceof ReferenceList) {
return ['r', value.tableId, value.rowIds]; return [GristObjCode.ReferenceList, value.tableId, value.rowIds];
} else if (value instanceof Date) { } else if (value instanceof Date) {
const timestamp = value.valueOf() / 1000; const timestamp = value.valueOf() / 1000;
if ('timezone' in value) { if ('timezone' in value) {
return ['D', timestamp, (value as GristDateTime).timezone]; return [GristObjCode.DateTime, timestamp, (value as GristDateTime).timezone];
} else { } else {
// TODO Depending on how it's used, may want to return ['d', timestamp] for UTC midnight. // 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) { } else if (value instanceof CensoredValue) {
return ['C']; return [GristObjCode.Censored];
} else if (value instanceof RaisedException) { } 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)) { } else if (Array.isArray(value)) {
return ['L', ...value.map(encodeObject)]; return [GristObjCode.List, ...value.map(encodeObject)];
} else if (isPlainObject(value)) { } 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) { } catch (e) {
// Fall through to return a best-effort representation. // 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 // 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 an "UnmarshallableValue" object, with repr() of the value to show to the user.
return ['U', UnknownValue.safeRepr(value)]; return [GristObjCode.Unmarshallable, UnknownValue.safeRepr(value)];
} }

@ -13,6 +13,7 @@ import {BulkColValues, DocAction, TableColValues, TableDataAction, toTableDataAc
import * as gristTypes from 'app/common/gristTypes'; import * as gristTypes from 'app/common/gristTypes';
import * as marshal from 'app/common/marshal'; import * as marshal from 'app/common/marshal';
import * as schema from 'app/common/schema'; import * as schema from 'app/common/schema';
import {GristObjCode} from "app/plugin/GristData";
import {ActionHistoryImpl} from 'app/server/lib/ActionHistoryImpl'; import {ActionHistoryImpl} from 'app/server/lib/ActionHistoryImpl';
import {ExpandedQuery} from 'app/server/lib/ExpandedQuery'; import {ExpandedQuery} from 'app/server/lib/ExpandedQuery';
import {IDocStorageManager} from 'app/server/lib/IDocStorageManager'; 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 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 { export class DocStorage implements ISQLiteDB {

@ -1,9 +1,10 @@
import {ServerQuery} from 'app/common/ActiveDocAPI'; import { ServerQuery } from 'app/common/ActiveDocAPI';
import {ApiError} from 'app/common/ApiError'; import { ApiError } from 'app/common/ApiError';
import {DocData} from 'app/common/DocData'; import { DocData } from 'app/common/DocData';
import {parseFormula} from 'app/common/Formula'; import { parseFormula } from 'app/common/Formula';
import {removePrefix} from 'app/common/gutil'; import { removePrefix } from 'app/common/gutil';
import {quoteIdent} from 'app/server/lib/SQLiteDB'; import { GristObjCode } from 'app/plugin/GristData';
import { quoteIdent } from 'app/server/lib/SQLiteDB';
/** /**
* Represents a query for Grist data with support for SQL-based * 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 // step. That means we need to pass the error message along
// explicitly. // explicitly.
constants?: { 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. // 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. // We add a trivial selection, and store errors in the query for substitution later.
sqlFormula = '0'; sqlFormula = '0';
if (!query.constants) { query.constants = {}; } if (!query.constants) { query.constants = {}; }
query.constants[colId] = ['E', error]; query.constants[colId] = [GristObjCode.Exception, error];
} }
if (sqlFormula) { if (sqlFormula) {
selects.add(`${sqlFormula} as ${quoteIdent(colId)}`); selects.add(`${sqlFormula} as ${quoteIdent(colId)}`);

@ -19,6 +19,7 @@ import { getSetMapValue, isObject, pruneArray } from 'app/common/gutil';
import { canEdit, canView, isValidRole, Role } from 'app/common/roles'; import { canEdit, canView, isValidRole, Role } from 'app/common/roles';
import { FullUser, UserAccessData } from 'app/common/UserAPI'; import { FullUser, UserAccessData } from 'app/common/UserAPI';
import { HomeDBManager } from 'app/gen-server/lib/HomeDBManager'; import { HomeDBManager } from 'app/gen-server/lib/HomeDBManager';
import { GristObjCode } from 'app/plugin/GristData';
import { compileAclFormula } from 'app/server/lib/ACLFormula'; import { compileAclFormula } from 'app/server/lib/ACLFormula';
import { DocClients } from 'app/server/lib/DocClients'; import { DocClients } from 'app/server/lib/DocClients';
import { getDocSessionAccess, getDocSessionUser, OptDocSession } from 'app/server/lib/DocSession'; import { getDocSessionAccess, getDocSessionUser, OptDocSession } from 'app/server/lib/DocSession';
@ -1104,9 +1105,9 @@ export class GranularAccess implements GranularAccessForBundle {
if (colValues === undefined) { if (colValues === undefined) {
censorAt = () => 1; censorAt = () => 1;
} else if (Array.isArray(action[2])) { } 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 { } 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. // These map an index of a row in the action to its index in rowsBefore and in rowsAfter.

@ -12,8 +12,8 @@ _ts_types = {
"Int": "number", "Int": "number",
"PositionNumber": "number", "PositionNumber": "number",
"Ref": "number", "Ref": "number",
"RefList": "['L', ...number[]]|null", # Non-primitive values are encoded "RefList": "[GristObjCode.List, ...number[]]|null", # Non-primitive values are encoded
"ChoiceList": "['L', ...string[]]|null", "ChoiceList": "[GristObjCode.List, ...string[]]|null",
"Text": "string", "Text": "string",
} }
@ -24,6 +24,9 @@ def get_ts_type(col_type):
def main(): def main():
print("""\ print("""\
/*** THIS FILE IS AUTO-GENERATED BY %s ***/ /*** THIS FILE IS AUTO-GENERATED BY %s ***/
import { GristObjCode } from "app/plugin/GristData";
// tslint:disable:object-literal-key-quotes // tslint:disable:object-literal-key-quotes
export const SCHEMA_VERSION = %d; export const SCHEMA_VERSION = %d;

Loading…
Cancel
Save