-
Notifications
You must be signed in to change notification settings - Fork 5
Lib data members fix1 #604
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
xeus2001
wants to merge
57
commits into
lib_data
Choose a base branch
from
lib_data_members_fix1
base: lib_data
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
57 commits
Select commit
Hold shift + click to select a range
77970cf
Add skill for AI to run tests.
xeus2001 7a0451f
Minor fix in delete and update feature tests.
xeus2001 d5ac124
Add JSON path to standard members.
xeus2001 7432781
Replaced hardcoded strings with StandardMembers references.
xeus2001 6501f6b
Import StandardMembers
xeus2001 efab25b
Renamed map into path in members, because thats what it actually is.
xeus2001 a763efb
Improve the member handling, partially removed the hardcoded workarou…
xeus2001 a0b1d0b
Little architectural overview generated by AI.
xeus2001 69df3e3
Fix errors left over from conflicts after rebasing.
xeus2001 06e3fa3
Improve member handling, so that the path is always used and features…
xeus2001 3ebe8e3
Add support for BookType, delete metadata and repalce with members. F…
xeus2001 64abbf4
Fix compilation errors that were the result of the previous modificat…
xeus2001 7879c57
Latest state, work in progress.
xeus2001 2cac66d
Fix minor issue in JBON2 spec.
xeus2001 c512529
Next bunch of fixes about the members not being hardcoded.
xeus2001 cdfc307
Handle Xyz members (#606)
kkin-here e5fdf06
Next round of AI code cleanup, now members are as they should be, sti…
xeus2001 6b7cb81
Fix more issues in PgRows and related.
xeus2001 b006742
Some more fixes.
xeus2001 5c56227
Fix catalog, add some comments where needed, deprecate usage of encod…
xeus2001 4db5ad6
More fixes in PgCatalog, session, transaction, aso.
xeus2001 5ae2aa7
Fix more compiler issues.
xeus2001 739d1cc
More minor fixes, mainly naming and comments.
xeus2001 998e890
Rename more maps into catalogs
xeus2001 426d3c6
Start fixing query builder.
xeus2001 c63ae99
fix Write classes
gunplar 2d0c875
Add dedicated members as helper, specifically for query convertion.
xeus2001 e2ac769
Add support for member queries.
xeus2001 c864b30
Ensure that we consistently talk about TagList and TagMap.
xeus2001 808f637
Add missing operations.
xeus2001 be9e9e1
Update JBON2 examples.
xeus2001 c5e3559
Add queryMembers to ReadFeatures as new member query, add converter f…
xeus2001 95cc887
Moved code to correct placed.
xeus2001 71a9d4b
Fixed PgQueryBuilder
xeus2001 e9ca517
Fix PgQueryBuilder and lots of small issues, like JS annotations.
xeus2001 f079cb3
Fix more issues.
xeus2001 bab6ac2
Fix PgWriteDelete.
xeus2001 eee40c6
Improve ID verification.
xeus2001 48c8275
Fix PgWriter.
xeus2001 7ac9ab3
Add query converter methods for new members (#607)
kkin-here 4e9de95
implement TagList ops
gunplar 192293c
Fix id usage in delete, fix insert
xeus2001 7c53c4c
Fix parts of upsert, minor improvements to members and heap book.
xeus2001 5929508
implement TagList ops
gunplar 1b2ca10
Fixed WriterUpsert
xeus2001 2f56218
Fix issues in WriterUpdate
xeus2001 0ee5df5
Final fixes for update.
xeus2001 12a50a5
Add missing documentation to members.
xeus2001 8125be5
Add equals infix operator to member and allow TupleNumber.fromByteArr…
xeus2001 0c01548
Make default XYZ member being typed members.
xeus2001 96f9e16
Fix compilation errors of test.
xeus2001 e4b89ee
Update TagList to be a string-list.
xeus2001 1284acf
AIs fixes of tests compilation.
xeus2001 4df4225
Fix indices
xeus2001 c510861
Fix compilation errors.
xeus2001 5bd995f
Add support in the get methods to read from Tuple, next to read from …
xeus2001 ff27444
fix some activitylog tests
gunplar File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| --- | ||
| name: naksha-test | ||
| description: Use ONLY when asked to run tests for the Naksha project. Do NOT use for other projects or general testing questions. | ||
| --- | ||
|
|
||
| # General | ||
| Most tests require a database. If the tests are executed without environment variables, they will start docker containers. When unclear, ask the user if they want to run the tests using automatically created Docker containers, or if they prefer to run the tests against their own, possibly local, PostgresQL test database. | ||
|
|
||
| # Environment Variables | ||
| All environment variables contain some placeholders that need to be replaced: | ||
|
|
||
| - `{host}`: The host of the PostgresQL cluster. If not given any other instructions, assume `localhost`. | ||
| - `{port}`: Needs to be replaced by you with the port at which the database is listening. If not given any other instructions, assume `5432`. | ||
| - `{user}`: Needs to be replaced by you with the user. If not given any other instructions, assume `postgres`. | ||
| - `{password}`: Needs to be replaced by you with the password. If not given any other instructions, assume `password`. | ||
|
|
||
| You can test the connection to the database. If you detect that the connection to the database fails due to wrong credentials or hostname, ask the user for host, port, user, and password _(whatever is needed)_. Use defaults for any value not provided. Tell the user the defaults. | ||
|
|
||
| ## Library tests (here-naksha-lib-psql) | ||
| Only needs one variable. If not set, Docker auto-starts: | ||
|
|
||
| ```bash | ||
| export NAKSHA_TEST_PSQL_DB_URL="jdbc:postgresql://{host}:{port}/postgres?user={user}&password={password}&ssl=false" | ||
| ``` | ||
|
|
||
| ## Server tests (here-naksha-app-service) | ||
| Needs all variables. These tests require a running Naksha server and will fail without one. Skip unless explicitly asked: | ||
|
|
||
| ```bash | ||
| export NAKSHA_APP_SERVICE_TEST_CONTEXT=LOCAL_STANDALONE | ||
| export NAKSHA_TEST_STORAGE_ID=local_psql_test_storage | ||
| export HUB_ADMIN_STORAGE_ID=local_psql_test_storage | ||
| export NAKSHA_TEST_PSQL_DB_URL="jdbc:postgresql://{host}:{port}/postgres?user={user}&password={password}&ssl=false" | ||
| export NAKSHA_TEST_ADMIN_DB_URL="jdbc:postgresql://{host}:{port}/postgres?user={user}&password={password}&ssl=false" | ||
| export NAKSHA_TEST_DATA_DB_URL="jdbc:postgresql://{host}:{port}/postgres?user={user}&password={password}&ssl=false" | ||
| ``` | ||
|
|
||
| # Commands | ||
|
|
||
| ## All library tests (JVM): | ||
| Docker auto-starts if no env vars are set: | ||
|
|
||
| ```bash | ||
| ./gradlew :here-naksha-lib-model:jvmTest :here-naksha-lib-psql:jvmTest :here-naksha-lib-jbon:jvmTest :here-naksha-lib-geo:jvmTest | ||
| ``` | ||
|
|
||
| ## All JVM tests (includes server tests that will fail without a running server): | ||
| Docker auto-starts if no env vars are set. This includes `here-naksha-app-service:jvmTest` which requires a running Naksha server and will fail with `ConnectException` if no server is available: | ||
|
|
||
| ```bash | ||
| ./gradlew jvmTest | ||
| ``` | ||
|
|
||
| ## All library tests (JS): | ||
| ```bash | ||
| ./gradlew :here-naksha-lib-model:jsTest :here-naksha-lib-jbon:jsTest :here-naksha-lib-geo:jsTest | ||
| ``` | ||
|
|
||
| ## Server tests | ||
| Only run if user explicitly asks. Requires a running Naksha server: | ||
|
|
||
| ```bash | ||
| ./gradlew :here-naksha-app-service:jvmTest | ||
| ``` | ||
|
|
||
| # Common Issues | ||
| - Kotlintest discovery errors: If `here-naksha-lib-psql:jvmTest` fails with test discovery errors, try `./gradlew clean` first | ||
| - Docker not available: The psql tests require Docker. If Docker isn't running, set `NAKSHA_TEST_PSQL_DB_URL` to an external Postgres instance | ||
| - Port conflicts: The Docker container uses host port 15432. If this port is in use, the container will fail to start | ||
| - Server tests fail with ConnectException: This is expected when no Naksha server is running. Skip these tests unless the server is available | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,206 @@ | ||
| # Architecture Overview | ||
|
|
||
| ## Architecture Overview | ||
|
|
||
| The codebase follows a **two-layer abstraction** pattern: | ||
|
|
||
| ### Layer 1: `lib-model` — Storage-agnostic interfaces | ||
| - **`ISession`** → core session API with `execute(request: Request): Response` | ||
| - **`IStorage`** → storage lifecycle, session creation | ||
| - **`IWriteSession`** → extends read session with `commit()`, `rollback()`, `useTransaction()` | ||
| - **`StorageTx`** → tuple encoding/decoding, member building | ||
| - **`AbstractStorage`** → base class all storages must extend (caching, lifecycle) | ||
|
|
||
| ### Layer 2: `lib-psql` — PostgreSQL implementation | ||
| - **`PgSession`** → implements `ISession`, manages PG connections | ||
| - **`PgStorage`** → extends `AbstractStorage`, manages PG connection pools | ||
| - **`PgWriter`** → stateful writer, dispatches to operation-specific classes | ||
| - **`PgWriterInsert/Upsert/Update/Delete`** → SQL generation per operation | ||
|
|
||
| ## Code Flow: Writing a New Feature | ||
|
|
||
| ``` | ||
| Client Code | ||
| │ | ||
| ├── 1. Build Write instruction | ||
| │ Write().createFeature(collection, feature) | ||
| │ Write().upsertFeature(mapId, colId, feature) | ||
| │ └── Sets: mapId, collectionId, op=CREATE/UPSERT, feature | ||
| │ | ||
| ├── 2. Wrap in WriteRequest | ||
| │ WriteRequest().add(write) | ||
| │ | ||
| ├── 3. Execute on session | ||
| │ session.execute(writeRequest) | ||
| │ │ | ||
| │ ├── PgSession.execute() routes to writer | ||
| │ │ writer = PgWriter(session, useSavepoint) | ||
| │ │ writer.execute(request.writes) | ||
| │ │ │ | ||
| │ │ ├── prepareWrite() | ||
| │ │ │ ├── Resolves mapId → PgMap (from adminMap cache/DB) | ||
| │ │ │ ├── Resolves colId → PgCollection | ||
| │ │ │ ├── For map/collection creates: calls createPgMap/createPgCollection | ||
| │ │ │ └── For features: builds PgWrite wrapper | ||
| │ │ │ | ||
| │ │ ├── groupOperations() | ||
| │ │ │ ├── Groups writes by map → collection → partition → op | ||
| │ │ │ └── For CREATE/UPSERT/UPDATE: calls StorageTx.created()/updated() | ||
| │ │ │ └── Builds Tuple: | ||
| │ │ │ ├── buildMembers() → IBook with metadata (updated_at, author, hash, etc.) | ||
| │ │ │ ├── Encodes feature (Naksha.encodeFeature → JBON/JSON bytes) | ||
| │ │ │ ├── Encodes geometry (Naksha.encodeGeometry → TWKB bytes) | ||
| │ │ │ └── Returns Tuple(storage#, map#, col#, fn, version, members, feature) | ||
| │ │ │ | ||
| │ │ └── executeWrite(map, col, partition, byOp) | ||
| │ │ ├── PgWriterInsert.execute(conn) → INSERT SQL | ||
| │ │ ├── PgWriterUpsert.execute(conn) → CTE-based UPSERT SQL | ||
| │ │ ├── PgWriterUpdate.execute(conn) → UPDATE with version check | ||
| │ │ └── PgWriterDelete.execute(conn) → tombstone/PURGE SQL | ||
| │ │ | ||
| │ └── Returns SuccessResponse with tuple numbers | ||
| │ | ||
| └── 4. Commit | ||
| session.commit() | ||
| ├── Persists transaction record to admin map | ||
| └── conn.commit() | ||
| ``` | ||
|
|
||
| ## Key Abstractions for New Features | ||
|
|
||
| | Concept | File | Purpose | | ||
| |---------|------|---------| | ||
| | **`Write`** | `lib-model/..request/Write.kt` | DSL for CRUD ops: `createFeature()`, `upsertFeature()`, etc. | | ||
| | **`WriteOp`** | `lib-model/..request/WriteOp.kt` | Enum: CREATE, UPSERT, UPDATE, DELETE, PURGE | | ||
| | **`Tuple`** | `lib-model/../Tuple.kt` | Immutable feature state: address (storage/map/col/fn/version) + members + feature bytes | | ||
| | **`StorageTx`** | `lib-model/../StorageTx.kt` | Builds Tuples from features: `created()`, `updated()`, `deleted()` | | ||
| | **`IMemberProcessor`** | `lib-model/../IMemberProcessor.kt` | Extension point for pre-persistence member mutation | | ||
| | **`PgWriter`** | `lib-psql/../PgWriter.kt` | Groups writes, dispatches to op-specific writers | | ||
| | **`PgWriterInsert`** | `lib-psql/../PgWriterInsert.kt` | SQL INSERT generation | | ||
| | **`PgWriterUpsert`** | `lib-psql/../PgWriterUpsert.kt` | CTE-based conditional insert/update | | ||
| | **`PgSession`** | `lib-psql/../PgSession.kt` | Connection management, `execute()` routing | | ||
|
|
||
| ## Extension Points | ||
|
|
||
| 1. **New storage backend**: Extend `AbstractStorage`, implement `ISession`, `PgWriter`-equivalent classes | ||
| 2. **Custom member processing**: `session.addMemberProcessor(memberName, processor)` — hooks into pre-persistence pipeline | ||
| 3. **New write operations**: Add to `WriteOp` enum, create new `PgWriter*` class, add dispatch in `PgWriter.executeWrite()` | ||
|
|
||
| ## Members Extraction: From NakshaFeature to PostgreSQL Columns | ||
|
|
||
| ### Three-Stage Write Path | ||
|
|
||
| **Stage 1: `StorageTx.buildMembers()` → `IBook` (in-memory members dict)** | ||
|
|
||
| File: `lib-model/../StorageTx.kt:114-162` | ||
|
|
||
| During `PgWriter.groupOperations()`, each write calls `tx.created()`/`tx.updated()`/`tx.deleted()` which invokes `buildTuple()` → `buildMembers()`. A `HeapBook` is created with all standard members extracted from the `NakshaFeature`: | ||
|
|
||
| ``` | ||
| NakshaFeature | ||
| ├── feature.id → StandardMembers.Id | ||
| ├── feature.geometry → StandardMembers.Geometry (TWKB bytes) | ||
| ├── feature.referencePoint → StandardMembers.ReferencePoint (TWKB bytes) | ||
| ├── feature.properties.xyz.updatedAt → StandardMembers.UpdatedAt | ||
| ├── feature.properties.xyz.createdAt → StandardMembers.CreatedAt | ||
| ├── feature.properties.xyz.author → StandardMembers.Author | ||
| ├── feature.properties.xyz.authorTs → StandardMembers.AuthorTimestamp | ||
| ├── feature.properties.xyz.appId → StandardMembers.AppId | ||
| ├── feature.properties.xyz.changeCount → StandardMembers.ChangeCount (+1) | ||
| ├── feature.properties.xyz.tags → StandardMembers.Tags (JSON string) | ||
| ├── feature.properties.xyz.hash → StandardMembers.Hash (computed) | ||
| ├── feature.properties.xyz.hereTile → StandardMembers.HereTile (computed) | ||
| ├── feature.properties.xyz.featureType → StandardMembers.FeatureType | ||
| ├── feature.properties.xyz.cv0-3 → StandardMembers.CustomValue0-3 | ||
| ├── feature.properties.xyz.cs0-3 → StandardMembers.CustomString0-3 | ||
| └── attachment → StandardMembers.Attachment | ||
| ``` | ||
|
|
||
| The resulting `IBook` is stored on `Tuple.members`. | ||
|
|
||
| **Stage 2: `PgColumnRows[row] = tuple` → Members into column arrays** | ||
|
|
||
| File: `lib-psql/../PgColumnRows.kt:384-417` | ||
|
|
||
| In the `PgWriterInsert`/`PgWriterUpsert`/`PgWriterUpdate` constructors, the `inRows` (`PgColumnRows`) is populated: | ||
|
|
||
| ```kotlin | ||
| // PgWriterInsert.kt:30-37 | ||
| for (write in writes) { | ||
| val tuple = write.tuple | ||
| if (tuple != null) { | ||
| inRows[i] = tuple // extracts IBook → column arrays | ||
| inRows.setCustomMembers(i, write.feature, members) // custom members | ||
| i++ | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| `PgColumnRows.set(row, tuple)` walks `tuple.members` by name and assigns each value into the corresponding typed column array (e.g., `set(row, PgColumn.updated_at, members.getByName("updated_at") as? Int64)`). | ||
|
|
||
| **Stage 3: `inRows.values()` → PostgreSQL UNNEST** | ||
|
|
||
| File: `lib-psql/../PgWriterInsert.kt:43-98` | ||
|
|
||
| The column arrays are passed as prepared statement parameters to a multi-row `UNNEST` INSERT: | ||
|
|
||
| ```sql | ||
| WITH new_row AS ( | ||
| SELECT * FROM UNNEST($1, $2, $3, ...) AS t(fn, version, id, feature, ...) | ||
| ) | ||
| INSERT INTO head_table (fn, version, id, feature, ...) | ||
| SELECT * FROM new_row | ||
| ``` | ||
|
|
||
| ### Custom Members Flow | ||
|
|
||
| File: `lib-psql/../PgCustomMemberValues.kt` | ||
|
|
||
| For user-declared custom members on the collection: | ||
|
|
||
| 1. **`PgWriterInsert.init`** → `inRows.addCustomMembers(collection.head.members)` — adds column entries for each custom member | ||
| 2. **`PgColumnRows.setCustomMembers(row, feature, members)`** — walks the feature using each member's `effectivePath()` via `PgCustomMemberValues.walkFeature()`, coerces the type via `PgCustomMemberValues.coerce()`, sets the column value | ||
| 3. **`PgColumnRows[row] = tuple`** does NOT handle custom members — only the built-in `StandardMembers` | ||
|
|
||
| ### Members Book Creation — All Locations | ||
|
|
||
| | Location | File:Line | Purpose | | ||
| |---------|-----------|---------| | ||
| | `StorageTx.buildMembers()` | `StorageTx.kt:139` | **Write path** — creates `HeapBook` from `NakshaFeature`, called during tuple construction in `PgWriter.groupOperations()` | | ||
| | `Naksha.decodeTuple()` | `Naksha.kt:520` | **Read path** — creates `HeapBook` when decoding a `Tuple` back into a `NakshaFeature` | | ||
| | `Naksha.decodeTuple()` | `Naksha.kt:581` | **Read path (alt)** — second decode path for `Tuple` → `NakshaFeature` | | ||
| | `PgColumnRows.getTuple()` | `PgColumnRows.kt:304` | **Read path** — creates `PgRowDict` (implements `IBook`) wrapping DB row columns, assigned to `Tuple.members` | | ||
|
|
||
| ### End-to-End Data Flow | ||
|
|
||
| ``` | ||
| WRITE PATH READ PATH | ||
| ┌─────────────────────┐ ┌──────────────────────┐ | ||
| NakshaFeature│ StorageTx │ │ PgRowDict │ | ||
| properties│ .buildMembers() │ │ (PgColumnRows[row]) │ | ||
| │ ───────────────── │ │ ─────────────────── │ | ||
| │ xyz.updatedAt ─────┼─→ IBook │ column "updated_at" │──→ Tuple.members.getByName() | ||
| │ xyz.author ───────┼─→ .put() │ column "author" │──→ NakshaFeature.xyz.author | ||
| │ geometry ───────┼─→ .put() │ column "geo" │──→ NakshaFeature.geometry | ||
| │ feature blob │ │ column "feature" │──→ NakshaFeature (decode) | ||
| └────────┌────────────┘ └──────────┬───────────┘ | ||
| │ │ | ||
| ▼ ▲ | ||
| ┌─────────────────────┐ ┌──────────────────────┐ | ||
| │ PgColumnRows │ │ PgColumnRows │ | ||
| │ .set(row, tuple) │──→ SQL │ .add(cursor) │ | ||
| │ .setCustomMembers()│ UNNEST │ .getTuple(row) │ | ||
| └─────────────────────┘ └──────────────────────┘ | ||
| │ ▲ | ||
| ▼ │ | ||
| ┌─────────────────────┐ ┌──────────────────────┐ | ||
| │ PostgreSQL │ │ PostgreSQL │ | ||
| │ HEAD table │ ────┐ │ HEAD/HISTORY │ | ||
| │ (fn, version, id, │ │ │ SELECT ... │ | ||
| │ feature, geo, │ │ │ FROM head_table │ | ||
| │ author, ...) │ │ │ WHERE ... │ | ||
| └─────────────────────┘ │ └──────────────────────┘ | ||
| │ | ||
| PostgreSQL DB │ | ||
| │ | ||
| ``` |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what I tried with my skills is to add a mention of self-evolution: something like "Update this skill If some instructions are out-of-date or there is new guidance/steps required". Usually it's enough to have agents self-evolve the skill, keeping it up-to-date