Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,17 @@ LIBSQLITE3_FLAGS = "-DSQLITE_ENABLE_MATH_FUNCTIONS"
[target.wasm32-unknown-unknown]
rustflags = [
"-C", "link-args=-z stack-size=16777216",
"-C", "link-args=--export-table --growable-table",
"-C", "link-args=--export=acos --export=asin --export=atan --export=atan2 --export=cos --export=exp --export=fmod --export=log --export=pow --export=sin --export=tan",
"-C", "link-args=--export=rust_sqlite_wasm_malloc --export=rust_sqlite_wasm_free --export=rust_sqlite_wasm_realloc --export=rust_sqlite_wasm_localtime --export=rust_sqlite_wasm_errno_location",
"-C", "link-args=--export=rust_sqlite_wasm_atoi --export=rust_sqlite_wasm_strtod --export=rust_sqlite_wasm_strtol --export=rust_sqlite_wasm_bsearch --export=rust_sqlite_wasm_qsort",
"-C", "link-args=--export=rust_sqlite_wasm_strcat --export=rust_sqlite_wasm_strchr --export=rust_sqlite_wasm_strcmp --export=rust_sqlite_wasm_strcpy --export=rust_sqlite_wasm_strlen --export=rust_sqlite_wasm_strncmp",
"-C", "link-args=--export=sqlite3_open_v2 --export=sqlite3_close --export=sqlite3_exec --export=sqlite3_prepare_v2 --export=sqlite3_step --export=sqlite3_reset --export=sqlite3_finalize",
"-C", "link-args=--export=sqlite3_bind_blob --export=sqlite3_bind_double --export=sqlite3_bind_int --export=sqlite3_bind_int64 --export=sqlite3_bind_null --export=sqlite3_bind_text",
"-C", "link-args=--export=sqlite3_column_blob --export=sqlite3_column_bytes --export=sqlite3_column_count --export=sqlite3_column_double --export=sqlite3_column_int64 --export=sqlite3_column_text --export=sqlite3_column_type",
"-C", "link-args=--export=sqlite3_value_double --export=sqlite3_value_int64 --export=sqlite3_value_type --export=sqlite3_result_double --export=sqlite3_result_int --export=sqlite3_result_null",
"-C", "link-args=--export=sqlite3_create_function --export=sqlite3_errmsg --export=sqlite3_config --export=sqlite3_initialize --export=sqlite3_last_insert_rowid --export=sqlite3_test_control",
"-C", "link-args=--export=sqlite3_malloc --export=sqlite3_free --export=sqlite3_mprintf --export=sqlite3_snprintf --export=sqlite3_vsnprintf --export=sqlite3_get_table --export=sqlite3_free_table",
"-C", "link-args=--export=sqlite3_libversion --export=sqlite3_libversion_number --export=sqlite3_uri_int64",
"-C", "link-args=--export=sqlite3_vfs_find --export=sqlite3_vfs_register --export=sqlite3_vfs_unregister",
]
19 changes: 1 addition & 18 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,25 +66,8 @@ jobs:
- name: Install Jupyter kernel
run: ./target/release/ggsql-jupyter --install

- name: Build WASM library
working-directory: ggsql-wasm/library
run: npm install && npm run build

- name: Build WASM package
working-directory: ggsql-wasm
run: wasm-pack build --target web --profile wasm --no-opt

- name: Optimise WASM binary
working-directory: ggsql-wasm
run: wasm-opt pkg/ggsql_wasm_bg.wasm -o pkg/ggsql_wasm_bg.wasm -Oz --all-features

- name: Build WASM demo
working-directory: ggsql-wasm/demo
run: npm install && npm run build

- name: Copying output to doc/wasm...
working-directory: ggsql-wasm/demo
run: cp -r dist ../../doc/wasm
run: ./ggsql-wasm/build-wasm.sh

- name: Copy CHANGELOG.md to root
run: cp CHANGELOG.md doc/
Expand Down
11 changes: 1 addition & 10 deletions .github/workflows/release-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -487,17 +487,8 @@ jobs:
- name: Install wasm-opt
run: cargo install wasm-opt

- name: Build WASM library
working-directory: ggsql-wasm/library
run: npm install && npm run build

- name: Build WASM package
working-directory: ggsql-wasm
run: wasm-pack build --target web --profile wasm --no-opt

- name: Optimise WASM binary
working-directory: ggsql-wasm
run: wasm-opt pkg/ggsql_wasm_bg.wasm -o pkg/ggsql_wasm_bg.wasm -Oz --all-features
run: ./ggsql-wasm/build-wasm.sh

- name: Create npm tarball
working-directory: ggsql-wasm/pkg
Expand Down
5 changes: 2 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ arrow = { version = "58", default-features = false }
duckdb = { version = "~1.10502", features = ["bundled", "vtab-arrow"] }
parquet = { version = "58", default-features = false, features = ["arrow", "snap"] }
bytes = "1"
rusqlite = { version = "0.38", features = ["bundled", "chrono"] }
rusqlite = { version = "0.38", features = ["bundled", "chrono", "load_extension"] }

# ODBC
toml_edit = "0.22"
Expand Down Expand Up @@ -80,3 +80,6 @@ strip = true
inherits = "release"
opt-level = "z"
panic = "abort"

[patch.crates-io]
sqlite-wasm-rs = { git = "https://github.com/ggsql-dev/sqlite-wasm-rs.git", branch = "loadable-extensions" }
6 changes: 4 additions & 2 deletions ggsql-wasm/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ ggsql-wasm/
│ └── src/ UI code (editor + Vega-Lite preview)
└── pkg/ wasm-pack output (committed; consumed by library/ and demo/)
├── ggsql_wasm_bg.wasm
├── mod_spatialite.wasm
├── ggsql_wasm.js, .d.ts
└── package.json
```
Expand Down Expand Up @@ -49,8 +50,9 @@ This sequentially:
1. `npm install && npm run build` in `library/` — produces the typed JS wrapper.
2. `wasm-pack build --target web --profile wasm --no-opt` — compiles `src/lib.rs` to `pkg/`. The `wasm` profile is defined in the workspace `Cargo.toml` (release-style, `opt-level = "z"`, LTO, `panic = "abort"`).
3. `wasm-opt pkg/ggsql_wasm_bg.wasm -o pkg/ggsql_wasm_bg.wasm -Oz` — shrinks the binary further.
4. `npm install && npm run build` in `demo/` — bundles the playground UI.
5. Copies `demo/dist/` to `/doc/wasm/` so Quarto can serve it under the docs site.
4. Downloads the prebuilt `mod_spatialite.wasm` from the [ggsql-dev/sqlite-wasm-rs releases](https://github.com/ggsql-dev/sqlite-wasm-rs/releases) into `pkg/`, caching it under `/target/wasm-extensions/`.
5. `npm install && npm run build` in `demo/` — bundles the playground UI (copies extension wasm from `pkg/` into `dist/`).
6. Copies `demo/dist/` to `/doc/wasm/` so Quarto can serve it under the docs site.

Flags:

Expand Down
4 changes: 2 additions & 2 deletions ggsql-wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
js-sys = "0.3"
arrow = { workspace = true }
ggsql = { path = "../src", default-features = false, features = ["vegalite", "sqlite", "builtin-data"] }
ggsql = { path = "../src", default-features = false, features = ["vegalite", "sqlite", "builtin-data", "spatial"] }
serde_json = "1"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1.35", features = ["full"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
tokio = { version = "1.35", default-features = false }
sqlite-wasm-rs = "0.5.2"
sqlite-wasm-rs = { version = "0.5.2", features = ["loadable-extensions"] }
# Transitive dep feature overrides for wasm32-unknown-unknown.
# Cargo's feature unification activates these on the transitive deps.
# - getrandom: pulled in by arrow (via ahash/const-random), needs "js" for wasm
Expand Down
45 changes: 44 additions & 1 deletion ggsql-wasm/build-wasm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,71 @@ check_wasm32_support() {
echo "Install an LLVM/clang toolchain with wasm backend support (e.g. 'sudo apt-get install llvm' on Debian/Ubuntu)." >&2
exit 1
fi
if ! command -v wasm-pack >/dev/null 2>&1; then
echo "Error: wasm-pack not found. Install with: cargo install wasm-pack" >&2
exit 1
fi
}

echo "Building WASM library..."
(cd "$SCRIPT_DIR/library" && npm install && npm run build)

if [ "$SKIP_BINARY" = false ]; then
echo "Checking wasm32 compiler support..."
echo "Checking wasm build prerequisites..."
check_wasm32_support

echo "Building WASM binary..."
rm -rf "$SCRIPT_DIR/pkg" # start clean so stale wasm-bindgen snippets don't accumulate
(cd "$SCRIPT_DIR" && wasm-pack build --target web --profile wasm --no-opt)

# wasm-bindgen is invoked directly so we can pass --keep-lld-exports,
# which preserves the LLD symbols that loadable extensions import.
# wasm-pack cannot forward that flag (rustwasm/wasm-pack#1092).
echo "Re-running wasm-bindgen with --keep-lld-exports..."
WASM_BINDGEN="$(find "$HOME/Library/Caches/.wasm-pack" "$HOME/.cache/.wasm-pack" -name wasm-bindgen -type f 2>/dev/null | sort -V | tail -1 || true)"
if [ -z "$WASM_BINDGEN" ]; then
echo "Error: could not locate wasm-pack's cached wasm-bindgen." >&2
exit 1
fi
"$WASM_BINDGEN" \
--target web \
--keep-lld-exports \
--out-dir "$SCRIPT_DIR/pkg" \
"$REPO_ROOT/target/wasm32-unknown-unknown/wasm/ggsql_wasm.wasm"

if [ "$SKIP_OPT" = false ]; then
echo "Optimising WASM binary..."
(cd "$SCRIPT_DIR" && wasm-opt pkg/ggsql_wasm_bg.wasm -o pkg/ggsql_wasm_bg.wasm -Oz --all-features)
else
echo "Skipping wasm-opt (--skip-opt)."
fi

echo "Adding snippets/ to package files..."
(cd "$SCRIPT_DIR/pkg" && npm pkg set 'files[]=snippets/')
else
echo "Skipping WASM binary build (--skip-binary)."
fi

SPATIALITE_TAG="spatialite-5.1.0-wasm"
SPATIALITE_URL="https://github.com/ggsql-dev/sqlite-wasm-rs/releases/download/$SPATIALITE_TAG/mod_spatialite.wasm"

# SPATIALITE_WASM overrides the download with a locally built binary.
if [ -n "${SPATIALITE_WASM:-}" ]; then
echo "Using local mod_spatialite.wasm: $SPATIALITE_WASM"
cp "$SPATIALITE_WASM" "$SCRIPT_DIR/pkg/mod_spatialite.wasm"
else
CACHED="$REPO_ROOT/target/wasm-extensions/$SPATIALITE_TAG/mod_spatialite.wasm"
if [ ! -f "$CACHED" ]; then
echo "Downloading mod_spatialite.wasm ($SPATIALITE_TAG)..."
mkdir -p "$(dirname "$CACHED")"
curl -sSfL -o "$CACHED.tmp" "$SPATIALITE_URL"
mv "$CACHED.tmp" "$CACHED"
else
echo "Using cached mod_spatialite.wasm: $CACHED"
fi
cp "$CACHED" "$SCRIPT_DIR/pkg/mod_spatialite.wasm"
fi

echo "Building WASM demo and Quarto integration..."
(cd "$SCRIPT_DIR/demo" && npm install && npm run build)

Expand Down
8 changes: 8 additions & 0 deletions ggsql-wasm/demo/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ copyFileSync(
join(__dirname, "../../ggsql-vscode/syntaxes/ggsql.tmLanguage.json"),
join(distDir, "ggsql.tmLanguage.json"),
);
for (const ext of ["mod_spatialite"]) {
try {
copyFileSync(
join(__dirname, `../pkg/${ext}.wasm`),
join(distDir, `${ext}.wasm`),
);
} catch (_) {}
}

// Build Monaco editor web worker
console.log("Building Monaco editor worker...");
Expand Down
12 changes: 12 additions & 0 deletions ggsql-wasm/demo/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 11 additions & 2 deletions ggsql-wasm/demo/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import init, { GgsqlContext } from "ggsql-wasm";
import init, {
GgsqlContext,
initExtensionLoader,
installExtension,
} from "ggsql-wasm";
import { WASM_BASE } from "./wasmBase";

export class WasmContextManager {
Expand All @@ -8,11 +12,16 @@ export class WasmContextManager {
async initialize(): Promise<void> {
if (this.initialized) return;

await init(WASM_BASE + "ggsql_wasm_bg.wasm");
const wasmExports = await init(WASM_BASE + "ggsql_wasm_bg.wasm");
initExtensionLoader(wasmExports);
this.context = new GgsqlContext();
this.initialized = true;
}

async installExtension(name: string, url: string): Promise<void> {
await installExtension(name, url);
}

private getContext(): GgsqlContext {
if (!this.context) {
throw new Error("Context not initialized. Call initialize() first.");
Expand Down
58 changes: 58 additions & 0 deletions ggsql-wasm/demo/src/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export interface Example {
name: string;
query: string;
section: string;
extensions?: string[];
}

export const examples: Example[] = [
Expand Down Expand Up @@ -211,4 +212,61 @@ VISUALISE
DRAW point MAPPING bill_len AS x, bill_dep AS y, body_mass AS size
LABEL title => 'Penguin Measurements', x => 'Bill Length (mm)', y => 'Bill Depth (mm)'`,
},

// === Spatial ===
{
section: "Spatial",
extensions: ["mod_spatialite"],
name: "World map",
query: `-- The spatial layer draws geographic geometries. The geometry column
-- of ggsql:world is detected automatically, so no mapping is needed.
VISUALISE FROM ggsql:world
DRAW spatial`,
},
{
section: "Spatial",
extensions: ["mod_spatialite"],
name: "Choropleth",
query: `-- Shade each country by a variable. Population is heavily skewed,
-- so a log scale makes the gradient readable.
VISUALISE FROM ggsql:world
DRAW spatial
MAPPING population AS fill
SETTING opacity => 1
SCALE fill TO viridis VIA log
LABEL title => 'Population by country', fill => 'Population'`,
},
{
section: "Spatial",
extensions: ["mod_spatialite"],
name: "Projection",
query: `-- PROJECT TO a named map projection. Robinson is a good default
-- for world maps; try mercator, mollweide, natural or eckert4.
VISUALISE continent AS fill FROM ggsql:world
DRAW spatial
PROJECT TO robinson`,
},
{
section: "Spatial",
extensions: ["mod_spatialite"],
name: "Globe",
query: `-- The orthographic projection shows the Earth as a globe. The origin
-- setting (lon, lat) chooses which hemisphere faces the viewer.
VISUALISE continent AS fill FROM ggsql:world
DRAW spatial
PROJECT TO orthographic
SETTING origin => (133.77, -25.27)`,
},
{
section: "Spatial",
extensions: ["mod_spatialite"],
name: "Regional map",
query: `-- Filtering the data zooms the map to that region, and a conic
-- projection like Lambert suits a single continent.
VISUALISE continent AS fill FROM ggsql:world
DRAW spatial
FILTER continent == 'Africa'
PROJECT TO lambert
SETTING origin => (20, 5)`,
},
];
Loading
Loading