Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
a82e02e
postgres bucket storage initial
stevensJourney Jan 8, 2025
a745f33
wip test factory
stevensJourney Jan 8, 2025
997ed4e
Add postgres storage to MySQL and Postgres replicator tests
stevensJourney Jan 9, 2025
3846a99
Fixes for MySQL tests: Store relation_id as text. Update BIGINT max …
stevensJourney Jan 9, 2025
ee891a8
Add Postgres storage to MongoDB replicator tests. Fix bug: last_check…
stevensJourney Jan 9, 2025
501df22
git move
stevensJourney Jan 9, 2025
a86472a
share logic in lib postgres
stevensJourney Jan 9, 2025
556b8d1
Add postgres storage to CI
stevensJourney Jan 9, 2025
63d8e3f
fix core test
stevensJourney Jan 9, 2025
a255cb7
dispose clients
stevensJourney Jan 9, 2025
7121d06
fix postgres slow tests
stevensJourney Jan 10, 2025
98f46a1
bump timeout
stevensJourney Jan 10, 2025
73de76e
fix truncation
stevensJourney Jan 10, 2025
0c682bb
attempt to fix racey mysql test
stevensJourney Jan 10, 2025
653626a
fix sync rules termination bug
stevensJourney Jan 10, 2025
9fd0481
cleanup
stevensJourney Jan 10, 2025
af47872
added changeset
stevensJourney Jan 10, 2025
198b6aa
improve sync performance
stevensJourney Jan 11, 2025
705b472
fix bug
stevensJourney Jan 11, 2025
dc76df1
cleanup
stevensJourney Jan 13, 2025
e64bde5
Merge remote-tracking branch 'origin/main' into pg-bucket-storage
stevensJourney Jan 13, 2025
7524d3b
cleanup
stevensJourney Jan 13, 2025
b670441
add env note
stevensJourney Jan 13, 2025
b9009d8
add primary keys to tables
stevensJourney Jan 13, 2025
07bbf19
update max row size to match mongo
stevensJourney Jan 13, 2025
6fbd9f8
set keepalive_op to bigint
stevensJourney Jan 13, 2025
40d434f
Cleanup sync_rules id sequence usage. Use integer/number type for gro…
stevensJourney Jan 13, 2025
405521e
fix lock conflict.
stevensJourney Jan 13, 2025
07ca4b7
move comment.
stevensJourney Jan 13, 2025
a06c715
update readme configuration. Remove batch settings from documentation.
stevensJourney Jan 13, 2025
c03e56e
better size estimation
stevensJourney Jan 13, 2025
fc39638
update to pg_relation_size to not include index size.
stevensJourney Jan 13, 2025
8c4a7e9
test pg_total_relation_size with vitest snapshot
stevensJourney Jan 13, 2025
efed534
add beta readme note
stevensJourney Jan 13, 2025
d17d896
update postgres replication storage snapshot
stevensJourney Jan 13, 2025
af9a6a1
cleanup slot poking
stevensJourney Jan 14, 2025
7cb338d
add note about ts-codec. Make buffer codex a bit clearer.
stevensJourney Jan 14, 2025
c517976
move pgwire_number to codecs file
stevensJourney Jan 14, 2025
79da9db
cleanup
stevensJourney Jan 14, 2025
d006e90
update bucket_parameters types
stevensJourney Jan 14, 2025
198fe30
store relation_id as json in order to store number or string type
stevensJourney Jan 14, 2025
91da74d
remove duplicated lines
stevensJourney Jan 14, 2025
7f3ea55
Updated BucketStorageFactory to use AsyncDisposable
stevensJourney Jan 14, 2025
c39ea45
added changeset
stevensJourney Jan 14, 2025
2318c6d
fix mongo tests
stevensJourney Jan 14, 2025
2d0f8fd
remove mystery todo update comment
stevensJourney Jan 14, 2025
444bc95
reset metrics between tests
stevensJourney Jan 14, 2025
483b94e
Improve migration logic.
stevensJourney Jan 15, 2025
5a723fd
fix typo
stevensJourney Jan 15, 2025
9556a47
cleanup
stevensJourney Jan 15, 2025
780770b
add public config in order for publish task to succeed
stevensJourney Jan 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/green-trainers-yell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@powersync/service-module-postgres-storage': minor
'@powersync/service-module-postgres': minor
'@powersync/lib-service-postgres': minor
---

Initial release of Postgres bucket storage.
3 changes: 2 additions & 1 deletion .env.template
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Connections for tests
MONGO_TEST_URL="mongodb://localhost:27017/powersync_test"
PG_TEST_URL="postgres://postgres:postgres@localhost:5432/powersync_test"
PG_TEST_URL="postgres://postgres:postgres@localhost:5432/powersync_test"
PG_STORAGE_TEST_URL="postgres://postgres:postgres@localhost:5431/powersync_storage_test"
36 changes: 36 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ jobs:
-d postgres:${{ matrix.postgres-version }} \
-c wal_level=logical

- name: Start PostgreSQL (Storage)
run: |
docker run \
--health-cmd pg_isready \
--health-interval 10s \
--health-timeout 5s \
--health-retries 5 \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=powersync_storage_test \
-p 5431:5432 \
-d postgres:${{ matrix.postgres-version }}

- name: Start MongoDB
uses: supercharge/[email protected]
with:
Expand Down Expand Up @@ -176,6 +188,18 @@ jobs:
mongodb-version: '6.0'
mongodb-replica-set: test-rs

- name: Start PostgreSQL (Storage)
run: |
docker run \
--health-cmd pg_isready \
--health-interval 10s \
--health-timeout 5s \
--health-retries 5 \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=powersync_storage_test \
-p 5431:5432 \
-d postgres:16

- name: Setup NodeJS
uses: actions/setup-node@v4
with:
Expand Down Expand Up @@ -229,6 +253,18 @@ jobs:
mongodb-version: ${{ matrix.mongodb-version }}
mongodb-replica-set: test-rs

- name: Start PostgreSQL (Storage)
run: |
docker run \
--health-cmd pg_isready \
--health-interval 10s \
--health-timeout 5s \
--health-retries 5 \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=powersync_storage_test \
-p 5431:5432 \
-d postgres:16

- name: Setup Node.js
uses: actions/setup-node@v4
with:
Expand Down
6 changes: 5 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@
"tabWidth": 2,
"useTabs": false,
"printWidth": 120,
"trailingComma": "none"
"trailingComma": "none",
"plugins": ["prettier-plugin-embed", "prettier-plugin-sql"],
"embeddedSqlTags": ["sql", "db.sql", "this.db.sql"],
"language": "postgresql",
"keywordCase": "upper"
}
1 change: 1 addition & 0 deletions libs/lib-postgres/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# @powersync/lib-service-postgres
67 changes: 67 additions & 0 deletions libs/lib-postgres/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Functional Source License, Version 1.1, Apache 2.0 Future License

## Abbreviation

FSL-1.1-Apache-2.0

## Notice

Copyright 2023-2024 Journey Mobile, Inc.

## Terms and Conditions

### Licensor ("We")

The party offering the Software under these Terms and Conditions.

### The Software

The "Software" is each version of the software that we make available under these Terms and Conditions, as indicated by our inclusion of these Terms and Conditions with the Software.

### License Grant

Subject to your compliance with this License Grant and the Patents, Redistribution and Trademark clauses below, we hereby grant you the right to use, copy, modify, create derivative works, publicly perform, publicly display and redistribute the Software for any Permitted Purpose identified below.

### Permitted Purpose

A Permitted Purpose is any purpose other than a Competing Use. A Competing Use means making the Software available to others in a commercial product or service that:

1. substitutes for the Software;
2. substitutes for any other product or service we offer using the Software that exists as of the date we make the Software available; or
3. offers the same or substantially similar functionality as the Software.

Permitted Purposes specifically include using the Software:

1. for your internal use and access;
2. for non-commercial education;
3. for non-commercial research; and
4. in connection with professional services that you provide to a licensee using the Software in accordance with these Terms and Conditions.

### Patents

To the extent your use for a Permitted Purpose would necessarily infringe our patents, the license grant above includes a license under our patents. If you make a claim against any party that the Software infringes or contributes to the infringement of any patent, then your patent license to the Software ends immediately.

### Redistribution

The Terms and Conditions apply to all copies, modifications and derivatives of the Software.
If you redistribute any copies, modifications or derivatives of the Software, you must include a copy of or a link to these Terms and Conditions and not remove any copyright notices provided in or with the Software.

### Disclaimer

THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES, EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.

### Trademarks

Except for displaying the License Details and identifying us as the origin of the Software, you have no right under these Terms and Conditions to use our trademarks, trade names, service marks or product names.

## Grant of Future License

We hereby irrevocably grant you an additional license to use the Software under the Apache License, Version 2.0 that is effective on the second anniversary of the date we make the Software available. On or after that date, you may use the Software under the Apache License, Version 2.0, in which case the following will apply:

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
3 changes: 3 additions & 0 deletions libs/lib-postgres/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# PowerSync Service Postgres

Library for common Postgres logic used in the PowerSync service.
43 changes: 43 additions & 0 deletions libs/lib-postgres/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "@powersync/lib-service-postgres",
"repository": "https://github.com/powersync-ja/powersync-service",
"types": "dist/index.d.ts",
"version": "0.0.0",
"main": "dist/index.js",
"license": "FSL-1.1-Apache-2.0",
"type": "module",
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "tsc -b",
"build:tests": "tsc -b test/tsconfig.json",
"clean": "rm -rf ./dist && tsc -b --clean",
"test": "vitest"
},
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.js",
"default": "./dist/index.js"
},
"./types": {
"import": "./dist/types/types.js",
"require": "./dist/types/types.js",
"default": "./dist/types/types.js"
}
},
"dependencies": {
"@powersync/lib-services-framework": "workspace:*",
"@powersync/service-jpgwire": "workspace:*",
"@powersync/service-sync-rules": "workspace:*",
"@powersync/service-types": "workspace:*",
"p-defer": "^4.0.1",
"ts-codec": "^1.3.0",
"uri-js": "^4.4.1",
"uuid": "^9.0.1"
},
"devDependencies": {
"@types/uuid": "^9.0.4"
}
}
109 changes: 109 additions & 0 deletions libs/lib-postgres/src/db/connection/AbstractPostgresConnection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import * as framework from '@powersync/lib-services-framework';
import * as pgwire from '@powersync/service-jpgwire';
import * as t from 'ts-codec';

export type DecodedSQLQueryExecutor<T extends t.Codec<any, any>> = {
first: () => Promise<t.Decoded<T> | null>;
rows: () => Promise<t.Decoded<T>[]>;
};

export abstract class AbstractPostgresConnection<
Listener extends framework.DisposableListener = framework.DisposableListener
> extends framework.DisposableObserver<Listener> {
protected abstract baseConnection: pgwire.PgClient;

stream(...args: pgwire.Statement[]): AsyncIterableIterator<pgwire.PgChunk> {
return this.baseConnection.stream(...args);
}

query(script: string, options?: pgwire.PgSimpleQueryOptions): Promise<pgwire.PgResult>;
query(...args: pgwire.Statement[]): Promise<pgwire.PgResult>;
query(...args: any[]): Promise<pgwire.PgResult> {
return this.baseConnection.query(...args);
}

/**
* Template string helper which can be used to execute template SQL strings.
*/
sql(strings: TemplateStringsArray, ...params: pgwire.StatementParam[]) {
const { statement, params: queryParams } = sql(strings, ...params);

const rows = <T>(): Promise<T[]> =>
this.queryRows({
statement,
params: queryParams
});

const first = async <T>(): Promise<T | null> => {
const [f] = await rows<T>();
return f;
};

return {
execute: () =>
this.query({
statement,
params
}),
rows,
first,
decoded: <T extends t.Codec<any, any>>(codec: T): DecodedSQLQueryExecutor<T> => {
return {
first: async () => {
const result = await first();
return result && codec.decode(result);
},
rows: async () => {
const results = await rows();
return results.map((r) => {
return codec.decode(r);
});
}
};
}
};
}

queryRows<T>(script: string, options?: pgwire.PgSimpleQueryOptions): Promise<T[]>;
queryRows<T>(...args: pgwire.Statement[] | [...pgwire.Statement[], pgwire.PgExtendedQueryOptions]): Promise<T[]>;
async queryRows(...args: any[]) {
return pgwire.pgwireRows(await this.query(...args));
}

async *streamRows<T>(...args: pgwire.Statement[]): AsyncIterableIterator<T[]> {
let columns: Array<keyof T> = [];

for await (const chunk of this.stream(...args)) {
if (chunk.tag == 'RowDescription') {
columns = chunk.payload.map((c, index) => {
return c.name as keyof T;
});
continue;
}

if (!chunk.rows.length) {
continue;
}

yield chunk.rows.map((row) => {
let q: Partial<T> = {};
for (const [index, c] of columns.entries()) {
q[c] = row[index];
}
return q as T;
});
}
}
}

/**
* Template string helper function which generates PGWire statements.
*/
export const sql = (strings: TemplateStringsArray, ...params: pgwire.StatementParam[]): pgwire.Statement => {
const paramPlaceholders = new Array(params.length).fill('').map((value, index) => `$${index + 1}`);
const joinedQueryStatement = strings.map((query, index) => `${query} ${paramPlaceholders[index] ?? ''}`).join(' ');
return {
statement: joinedQueryStatement,
params
};
};
Loading
Loading