Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Code Block
breakoutModewide
breakoutWidth760
languagejs
const gridRowSchema = s.obj({
  data: s.vec(s.con('')),
  metadata: s.obj({
    synapseRow: s.obj({
      rowId: s.con(0),
      versionNumber: s.con(0),
      etag: s.con(''),
    }),
    rowValidation: s.con(0)
  }),
});

const gridSchema = s.obj({
  doc_version: s.con('0.1.0'),
  columnNames: s.vec(s.con('')),
  columnOrder: s.arr([s.con(0)]),
  rows: s.arr([gridRowSchema]),
});

name

description

doc_version

The semantic version of the document schema. This value will change anytime we change this schema.

columnNames

This vector captures the name of the columns in their natural order (order they were added). Vectors are append-only (up to 256 values). Vectors allow LWW changes to each index.

columnOrder

This array defines the order that the columns should be displayed. Each value is an index into the columnNames. The array can be reordered.

rows

This is an arr of ‘row’ objects. The arr is the mutable row order. Each obj contains two properties: data and metadata.

rows[*].data

The row data vec represents the data in a row. The order of the vec matches the columnNames vector (append-only). Each cell in a row’s vector can be changed to a new con. Cells with identical values will share a reference to a single constant (con) (typically).

rows[*].metadata

The row metadata is an obj that contains supplemental information about the row.

This data should be treated as read-only for all replicas other than the hub.

rows[*].metadata.synapseRow

The synapseRow object can be used to map a row back to its source from a Synapse Table or View.

rowValidation: s.con(0)

The rowValidation value is a reference to a constant containing the JSON serialization of a: ValidationResult. Note: The ValidationResult is identical to results provided for Entity Validation.

Note: Each vec of the rows is append-only (cannot be re-order) with each index matching an index from columnNames. When a new column name is appended to columnNames vector, each row’s vector will be smaller (by one), indicating that the cells of newly added columns are null. For such cases, row vectors only need to be extended when setting a cell value for the new column.

...

The server-side implementation of a grid replica will need to read/write the grid document to a database. Consider an example where the worker is tasked with applying a patch from a client to the database. Ideally, the patch would be applied directly to database without loading the entire document into memory.

The following is a working prototype of SQL that achieves the goal of applying a patch by writing to a SQL database:

Code Block
breakoutModewide
breakoutWidth760
languagesql
drop database grid1;
create database grid1;
USE grid1;
SET autocommit = 1; -- Patches are not transactions!

# We need to support tables of up to 100K rows.
SET SESSION cte_max_recursion_depth = 1000000;

-- The index of all nodes in the document.
CREATE TABLE R1_INDEX (
    NODE_REP BIGINT NOT NULL,
	NODE_SEQ BIGINT NOT NULL,
    KIND ENUM ('con','obj','vec','arr'),
	PRIMARY KEY (NODE_REP, NODE_SEQ)
);

-- This table is a simple linked list that stores the Replicated Growable Array (RGA).
-- Data is inserted into the table using 'call rga_insert(....)'
CREATE TABLE R1_ARR (
    CHUNK_REP BIGINT NOT NULL,
    CHUNK_SEQ BIGINT NOT NULL,
    NODE_REP BIGINT NOT NULL,
    NODE_SEQ BIGINT NOT NULL,
	PARENT_REP BIGINT,
    PARENT_SEQ BIGINT,
    TOMBSTONE BOOLEAN DEFAULT FALSE, -- on delete, set to true, indicating that the node was deleted.
	PRIMARY KEY (CHUNK_REP, CHUNK_SEQ),
	INDEX (PARENT_REP, PARENT_SEQ),
    CONSTRAINT `R1_ARR_PARENT_FK` FOREIGN KEY (PARENT_REP, PARENT_SEQ) REFERENCES R1_ARR(CHUNK_REP, CHUNK_SEQ) ON DELETE RESTRICT,
	CONSTRAINT `R1_ARR_NODE_INDEX_FK` FOREIGN KEY (NODE_REP, NODE_SEQ) REFERENCES R1_INDEX(NODE_REP, NODE_SEQ) ON DELETE RESTRICT
);

-- Each replica will have its own vec table.
-- Each row in this table is a single vector (vec) identify by its ID (VEC_SEQ,VEC_REP).
-- The vector is grown by adding columns.  The name of the columns is derived from the vector index ('_C' + <index> + '_')
-- Each index column is of type JSON and contains both the ID and value of a constant.
-- Updates to exsiting cells will use LWW check (update if the new ID is greater than the current ID).
CREATE TABLE R1_VEC (
    VEC_REP BIGINT NOT NULL,
	VEC_SEQ BIGINT NOT NULL,
	_C0_ JSON,
	PRIMARY KEY (VEC_REP, VEC_SEQ),
	CONSTRAINT `R1_VEC_INDEX_FK` FOREIGN KEY (VEC_REP, VEC_SEQ) REFERENCES R1_INDEX(NODE_REP, NODE_SEQ) ON DELETE RESTRICT
);

-- Store of all objects (obj) in the document.
CREATE TABLE R1_OBJ (
    OBJ_REP BIGINT NOT NULL,
	OBJ_SEQ BIGINT NOT NULL,
    _KEY VARCHAR(100) NOT NULL,
	VAL_REP BIGINT NOT NULL,
    VAL_SEQ BIGINT NOT NULL,
	PRIMARY KEY (OBJ_REP, OBJ_SEQ, _KEY),
	CONSTRAINT `R1_OBJ_INDEX_FK` FOREIGN KEY (OBJ_REP, OBJ_SEQ) REFERENCES R1_INDEX(NODE_REP, NODE_SEQ) ON DELETE RESTRICT
);

-- Store of all constants (con) in the document.
CREATE TABLE R1_CON (
    CON_REP BIGINT NOT NULL,
	CON_SEQ BIGINT NOT NULL,
    VAL JSON NOT NULL,
	PRIMARY KEY (CON_SEQ, CON_REP),
	CONSTRAINT `R1_CON_INDEX_FK` FOREIGN KEY (CON_REP, CON_SEQ) REFERENCES R1_INDEX(NODE_REP, NODE_SEQ) ON DELETE RESTRICT
);

DELIMITER //

-- Function to compare two logical timestamps.
CREATE FUNCTION compare_logical_timestamps (
    left_rep BIGINT,
    left_seq BIGINT,
    right_rep BIGINT,
    right_seq BIGINT
)
RETURNS INTEGER
DETERMINISTIC
BEGIN
	IF left_seq IS NULL THEN
		SET left_seq = -1;
	END IF;
	IF left_rep IS NULL THEN
		SET left_rep = -1;
	END IF;
	IF right_seq IS NULL THEN
		SET right_seq = -1;
	END IF;
	IF right_rep IS NULL THEN
		SET right_rep = -1;
	END IF;
    
    IF left_seq > right_seq THEN
        RETURN 1;
    ELSEIF left_seq < right_seq THEN
        RETURN -1;
    ELSE
        -- Sequence counters are equal, compare replica IDs
        IF left_rep > right_rep THEN
            RETURN 1;
        ELSEIF left_rep < right_rep THEN
            RETURN -1;
        ELSE
            RETURN 0;
        END IF;
    END IF;
END//

-- Create a new object with the provied id.
CREATE PROCEDURE new_obj (
    IN OBJ_REP BIGINT,
	IN OBJ_SEQ BIGINT
)
BEGIN
	INSERT IGNORE INTO R1_INDEX (NODE_REP, NODE_SEQ, KIND) VALUE (OBJ_REP, OBJ_SEQ, 'obj');
END//

CREATE PROCEDURE ins_obj (
    IN P_OBJ_REP BIGINT,
	IN P_OBJ_SEQ BIGINT,
    IN P_KEY VARCHAR(100),
    IN P_VAL_REP BIGINT,
    IN P_VAL_SEQ BIGINT
)
BEGIN
	DECLARE v_cur_rep BIGINT;
    DECLARE v_cur_seq BIGINT;
    
    SELECT VAL_REP, VAL_SEQ INTO v_cur_rep, v_cur_seq FROM R1_OBJ WHERE (OBJ_REP,OBJ_SEQ) = (P_OBJ_REP,P_OBJ_SEQ) AND _KEY = P_KEY;
    -- Last-Writer-Wins (LWW)
    IF compare_logical_timestamps(P_VAL_REP, P_VAL_SEQ, v_cur_rep,v_cur_seq) = 1 THEN
		INSERT INTO R1_OBJ (OBJ_REP, OBJ_SEQ, _KEY, VAL_REP, VAL_SEQ) VALUE (P_OBJ_REP, P_OBJ_SEQ, P_KEY,P_VAL_REP,P_VAL_SEQ)
        ON DUPLICATE KEY UPDATE VAL_REP = P_VAL_REP, VAL_SEQ = P_VAL_SEQ;
	END IF;
END//

-- Create a new constant.
CREATE PROCEDURE new_con (
    IN CON_REP BIGINT,
	IN CON_SEQ BIGINT,
    IN _VAL JSON
)
BEGIN
	INSERT IGNORE INTO R1_INDEX (NODE_REP, NODE_SEQ, KIND) VALUE (CON_REP, CON_SEQ, 'con');
	INSERT IGNORE INTO R1_CON (CON_REP, CON_SEQ, VAL) VALUE (CON_REP, CON_SEQ, _VAL);
END//

-- Create a new vec.
CREATE PROCEDURE new_vec (
    IN VEC_REP BIGINT,
	IN VEC_SEQ BIGINT
)
BEGIN
	INSERT IGNORE INTO R1_INDEX (NODE_REP, NODE_SEQ, KIND) VALUE (VEC_REP, VEC_SEQ, 'vec');
	INSERT IGNORE INTO R1_VEC (VEC_REP, VEC_SEQ) VALUE (VEC_REP, VEC_SEQ);
END//

-- Create a new arr.
CREATE PROCEDURE new_arr (
    IN ARR_REP BIGINT,
	IN ARR_SEQ BIGINT
)
BEGIN
	INSERT IGNORE INTO R1_INDEX (NODE_REP, NODE_SEQ, KIND) VALUE (ARR_REP, ARR_SEQ, 'arr');
	call ins_arr(ARR_REP,ARR_SEQ, ARR_REP,ARR_SEQ, NULL,NULL, ARR_REP,ARR_SEQ);
END//

CREATE PROCEDURE ins_vec (
    IN P_ID_REP BIGINT,
	IN P_ID_SEQ BIGINT,
    IN P_VEC_REP BIGINT,
	IN P_VEC_SEQ BIGINT,
    IN P_INDEX INT,
	IN P_VAL_REP BIGINT,
	IN P_VAL_SEQ BIGINT
)
BEGIN
	DECLARE v_colum_name CHAR(100);
	DECLARE v_existing_name CHAR(100);
    DECLARE v_con_json JSON;
	DECLARE v_new_json JSON;
    DECLARE v_cur_rep BIGINT;
    DECLARE v_cur_seq BIGINT;
    
    SET v_colum_name = CONCAT('_C',P_INDEX,'_');
    SELECT `COLUMN_NAME` into v_existing_name FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'grid1' AND TABLE_NAME = 'r1_vec' AND COLUMN_NAME = v_colum_name;
    IF v_existing_name IS NULL THEN
        -- Construct the ALTER TABLE statement dynamically
		SET @alterTable = CONCAT('ALTER TABLE R1_VEC ADD COLUMN `', v_colum_name, '` JSON');
        -- Prepare and execute the dynamic SQL
        PREPARE stmt FROM @alterTable;
        EXECUTE stmt;
        DEALLOCATE PREPARE stmt;
    END IF;
    
    SET @p_rep = P_VEC_REP;
	SET @p_seq = P_VEC_SEQ;
    -- Fetch existing rep and seq if the column exists
    SET @select_stmt = CONCAT('SELECT JSON_EXTRACT(`', v_colum_name, '`, \'$.rep\'), JSON_EXTRACT(`', v_colum_name, '`, \'$.seq\') INTO @v_cur_rep, @v_cur_seq FROM R1_VEC WHERE (VEC_REP, VEC_SEQ) = (?, ?)');
    PREPARE select_stmt FROM @select_stmt;
    EXECUTE select_stmt USING @p_rep, @p_seq;
    DEALLOCATE PREPARE select_stmt;
    SET v_cur_rep = @v_cur_rep;
    SET v_cur_seq = @v_cur_seq;
  
    -- Last-Writer-Wins (LLW) check.
    IF compare_logical_timestamps(P_VAL_REP, P_VAL_SEQ, v_cur_rep,v_cur_seq) = 1 THEN
		SELECT VAL INTO v_con_json FROM R1_CON WHERE CON_REP = P_VAL_REP AND CON_SEQ = P_VAL_SEQ;
        SET @v_new_json = JSON_OBJECT('rep', P_VAL_REP, 'seq', P_VAL_SEQ, 'con', JSON_EXTRACT(v_con_json, '$.con'));

        -- Use the dynamic column name in the UPDATE statement, quoting it
        SET @update_statement = CONCAT('UPDATE R1_VEC SET `', v_colum_name, '` = ? WHERE (VEC_REP, VEC_SEQ) = (?, ?)');
        PREPARE update_stmt FROM @update_statement;
        EXECUTE update_stmt USING @v_new_json, @p_rep, @p_seq;
        DEALLOCATE PREPARE update_stmt;
    END IF;
END//

-- Iplementaion of the https://jsonjoy.com/specs/json-crdt/model-document/crdt-algorithms#RGA-Insertion-Routine
CREATE PROCEDURE ins_arr (
    IN P_ID_REP BIGINT,
    IN P_ID_SEQ BIGINT,
    IN P_NODE_REP BIGINT,
    IN P_NODE_SEQ BIGINT,
    IN P_REF_REP BIGINT,
    IN P_REF_SEQ BIGINT,
    IN P_ELE_REP BIGINT,
    IN P_ELE_SEQ BIGINT
)
BEGIN
    DECLARE v_cursor_seq BIGINT;
    DECLARE v_cursor_rep BIGINT;
	DECLARE v_after_cursor_seq BIGINT;
    DECLARE v_after_cursor_rep BIGINT;
	DECLARE v_after_chunk_seq BIGINT;
    DECLARE v_after_chunk_rep BIGINT;
        
    -- 1. Insertion cursor is set to the position before all elements in the RGA node.
    SET v_cursor_seq = P_NODE_SEQ;
    SET v_cursor_rep = P_NODE_REP;
    
    -- 2. If the rga.ref is not equal to the rga.node, then the cursor is moved to the position right after the element with the rga.ref ID
    IF compare_logical_timestamps(P_NODE_REP, P_NODE_SEQ, P_REF_REP, P_REF_SEQ) <> 0 THEN
		SET v_cursor_seq = P_REF_SEQ;
        SET v_cursor_rep = P_REF_REP;
	END IF;
    -- 3. If the ID of the element after the cursor is greater than the ID of elem, then the cursor is moved one position forward and step 3 is repeated. Otherwise, continue to step 4.
	label1: LOOP
		SELECT NODE_SEQ, NODE_REP, CHUNK_SEQ, CHUNK_REP INTO v_after_cursor_seq, v_after_cursor_rep, v_after_chunk_seq, v_after_chunk_rep FROM R1_ARR WHERE (PARENT_SEQ, PARENT_REP) = (v_cursor_seq, v_cursor_rep);
        IF compare_logical_timestamps(v_after_cursor_rep, v_after_cursor_seq, P_ELE_REP, P_ELE_SEQ) = 1 THEN
			SET v_cursor_seq = v_after_cursor_seq;
            SET v_cursor_rep = v_after_cursor_rep;
		ELSE
			 LEAVE label1;
		END IF;
	END LOOP label1;
    
    -- 4. If the ID of the element after the cursor is equal to the ID of elem, then the insertion stops. The elements have already been inserted by a previous application of this algorithm. Otherwise, continue to step 5.
	IF compare_logical_timestamps(v_after_cursor_rep, v_after_cursor_seq, v_cursor_rep, v_cursor_seq) <> 0 OR (v_cursor_seq IS NULL AND v_cursor_rep IS NULL) THEN
		-- 5. Insert the elem at the cursor position.
		INSERT INTO R1_ARR (CHUNK_REP, CHUNK_SEQ, NODE_REP, NODE_SEQ, PARENT_REP, PARENT_SEQ) VALUE (P_ID_REP, P_ID_SEQ, P_ELE_REP, P_ELE_SEQ, v_cursor_rep, v_cursor_seq);
        -- the after cursor needs to be moved if it exists
        IF v_after_chunk_seq IS NOT NULL AND v_after_chunk_rep IS NOT NULL THEN
			UPDATE R1_ARR SET PARENT_REP = P_ID_REP, PARENT_SEQ = P_ID_SEQ WHERE (CHUNK_REP,CHUNK_SEQ) = (v_after_chunk_rep,v_after_chunk_seq);
        END IF;
    END IF;
END //


-- calls rga_insert TO_ADD times to populate a large grid.
CREATE PROCEDURE at_scale (
    IN TO_ADD BIGINT -- The number of nodes to add to the array
)
BEGIN
    DECLARE v_seq BIGINT;
    SET v_seq = 1;

    call rga_insert(1,1, NULL,NULL, v_seq,1);
    
	label1: LOOP
		SET v_seq = v_seq + 1;
		IF v_seq > TO_ADD THEN 
			LEAVE label1;
		END  IF;
        call rga_insert(1,1, (v_seq-1),1, v_seq,1);
	END LOOP label1;
END //

DELIMITER ;

Using the above tables/procedures we can apply the patch that was generated by the client:

Code Block
breakoutModewide
breakoutWidth760
languagesql
-- ├─ new_con 2.2 { "0.0.2" }
CALL new_con(2,2, '{ "con":"0.0.2" }');

-- ├─ new_vec 2.3
CALL new_vec(2,3);

-- ├─ new_con 2.4 { "" }
CALL new_con(2,4, '{ "con":"" }');

-- ├─ ins_vec 2.5!1, obj = 2.3
-- │  └─ 0: 2.4
CALL ins_vec(2,5, 2,3, 0, 2,4);

-- ├─ new_arr 2.6
CALL new_arr(2,6);

-- ├─ new_con 2.7 { 0 }
CALL new_con(2,7, '{ "con": 0 }');

-- ├─ ins_arr 2.8!1, obj = 2.6 { 2.6 ← 2.7 }
CALL ins_arr(2,8, 2,6, 2,6, 2,7);

-- ├─ new_arr 2.9
CALL new_arr(2,9);

-- ├─ new_vec 2.10
CALL new_vec(2,10);

-- ├─ new_con 2.11 { "" }
CALL new_con(2,11, '{ "con":"" }');

-- ├─ ins_vec 2.12!1, obj = 2.10
-- │  └─ 0: 2.11
CALL ins_vec(2,12, 2,10, 0, 2,11);

-- ├─ ins_arr 2.13!1, obj = 2.9 { 2.9 ← 2.10 }
CALL ins_arr(2,13, 2,9, 2,9, 2,10);

-- ├─ ins_obj 2.14!1, obj = 2.1
-- │  ├─ "doc_version": 2.2
-- │  ├─ "columnNames": 2.3
-- │  ├─ "columnOrder": 2.6
-- │  └─ "rows": 2.9
CALL ins_obj(2,1, 'doc_version', 2,2);
CALL ins_obj(2,1, 'columnNames', 2,3);
CALL ins_obj(2,1, 'columnOrder', 2,6);
CALL ins_obj(2,1, 'rows', 2,9);

-- ├─ ins_val 2.15!1, obj = 0.0, val = 2.1
# Not sure what to do with this.

-- ├─ new_con ..5536.16 { "type" }
CALL new_con(65536,16, '{ "con":"type" }');

-- ├─ ins_vec ..5536.17!1, obj = 2.3
-- │  └─ 0: ..5536.16
CALL ins_vec(65536,17, 2,3, 0, 65536,16);

-- ├─ new_con ..5536.18 { "dog" }
CALL new_con(65536,18, '{ "con":"dog" }');

-- ├─ ins_vec ..5536.19!1, obj = 2.10
-- │  └─ 0: ..5536.18
CALL ins_vec(65536,19, 2,10, 0, 65536,18);

-- ├─ new_con ..5536.20 { "age" }
CALL new_con(65536,20, '{ "con":"age" }');

-- ├─ ins_vec ..5536.21!1, obj = 2.3
-- │  └─ 1: ..5536.20
CALL ins_vec(65536,21, 2,3, 1, 65536,20);

-- ├─ new_con ..5536.22 { 1 }
CALL new_con(65536,22, '{ "con": 1 }');

-- ├─ ins_arr ..5536.23!1, obj = 2.6 { 2.8 ← ..5536.22 }
CALL ins_arr(65536,23, 2,6, 2,8, 65536,22);

-- ├─ new_con ..5536.24 { 9 }
CALL new_con(65536,24, '{ "con": 9 }');

-- ├─ ins_vec ..5536.25!1, obj = 2.10
-- │  └─ 1: ..5536.24
CALL ins_vec(65536,25, 2,10, 1, 65536,24);

-- ├─ new_con ..5536.26 { "name" }
CALL new_con(65536,26, '{ "con": "name" }');

-- ├─ ins_vec ..5536.27!1, obj = 2.3
-- │  └─ 2: ..5536.26
CALL ins_vec(65536,27, 2,3, 2, 65536,26);

-- ├─ new_con ..5536.28 { 2 }
CALL new_con(65536,28, '{ "con": 2 }');

-- ├─ ins_arr ..5536.29!1, obj = 2.6 { 2.8 ← ..5536.28 }
CALL ins_arr(65536,29, 2,6, 2,8, 65536,28);

-- ├─ new_con ..5536.30 { "max" }
CALL new_con(65536,30, '{ "con": "max" }');

-- ├─ ins_vec ..5536.31!1, obj = 2.10
-- │  └─ 2: ..5536.30
CALL ins_vec(65536,31, 2,10, 2, 65536,30);

-- ├─ new_vec ..5536.32
CALL new_vec(65536,32);

-- ├─ ins_arr ..5536.33!1, obj = 2.9 { 2.13 ← ..5536.32 }
CALL ins_arr(65536,33, 2,9, 2,13, 65536,32);

-- ├─ new_con ..5536.34 { "cat" }
CALL new_con(65536,34, '{ "con": "cat" }');

-- ├─ new_con ..5536.35 { 15 }
CALL new_con(65536,35, '{ "con": 15 }');

-- ├─ new_con ..5536.36 { "paws" }
CALL new_con(65536,36, '{ "con": "paws" }');

-- ├─ ins_vec ..5536.37!1, obj = ..5536.32
-- │  ├─ 0: ..5536.34
-- │  ├─ 1: ..5536.35
-- │  └─ 2: ..5536.36
CALL ins_vec(65536,37, 65536,32, 0, 65536,34);
CALL ins_vec(65536,37, 65536,32, 1, 65536,35);
CALL ins_vec(65536,37, 65536,32, 2, 65536,36);

-- ├─ new_vec ..5536.38
CALL new_vec(65536,38);

-- ├─ ins_arr ..5536.39!1, obj = 2.9 { 2.13 ← ..5536.38 }
CALL ins_arr(65536,39, 2,9, 2,13, 65536,38);

-- ├─ new_con ..5536.40 { "rat" }
CALL new_con(65536,40, '{ "con": "rat" }');

-- ├─ new_con ..5536.41 { 2 }
CALL new_con(65536,41, '{ "con": 2 }');

-- ├─ new_con ..5536.42 { "whiskers" }
CALL new_con(65536,42, '{ "con": "whiskers" }');

-- └─ ins_vec ..5536.43!1, obj = ..5536.38
--    ├─ 0: ..5536.40
--    ├─ 1: ..5536.41
--    └─ 2: ..5536.42
CALL ins_vec(65536,43, 65536,38, 0, 65536,40);
CALL ins_vec(65536,43, 65536,38, 1, 65536,41);
CALL ins_vec(65536,43, 65536,38, 2, 65536,42);

After applying this patch to the database the following SQL can be used to show the “view” of the grid document:

Code Block
breakoutModewide
breakoutWidth760
languagesql
WITH RECURSIVE D AS (
    SELECT CHUNK_REP,CHUNK_SEQ, NODE_REP, NODE_SEQ, PARENT_REP, PARENT_SEQ, 1 AS level
		FROM R1_ARR WHERE (CHUNK_REP,CHUNK_SEQ) = (2,9)
    UNION ALL
    SELECT D2.CHUNK_REP, D2.CHUNK_SEQ, D2.NODE_REP, D2.NODE_SEQ, D2.PARENT_REP, D2.PARENT_SEQ, D.level + 1
		FROM D INNER JOIN R1_ARR D2 ON (D.CHUNK_REP, D.CHUNK_SEQ) = (D2.PARENT_REP, D2.PARENT_SEQ)
)
SELECT concat(D.NODE_REP, '.', D.NODE_SEQ) id, 
	JSON_EXTRACT(V._C0_, '$.con' ) "type",
    JSON_EXTRACT(V._C2_, '$.con' ) "name",
	JSON_EXTRACT(V._C1_, '$.con' ) "age" 
	FROM D JOIN R1_VEC V ON (D.NODE_REP,D.NODE_SEQ) = (V.VEC_REP,V.VEC_SEQ)  ORDER BY level ASC;

Results:

id

type

name

age

2.10

"dog"

"max"

9

65536.38

"rat"

"whiskers"

2

65536.32

"cat"

"paws"

15

This table matches our expected “view” of the example gridA server-side replica is maintained for each grid session by writing patches into the following database tables: Grid Schemas. A “view” of the grid document is generated using a specialized query that joins the grid tables using the following template: grid-index-view-template.sql.

Grid Communication Flow

The following diagram provides a rough outline of the communication flow of the current grid implementation:

...