Skip to content
Closed
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
2 changes: 1 addition & 1 deletion Cargo.lock

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

12 changes: 11 additions & 1 deletion PARITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -516,12 +516,22 @@ those land.
| CLI reached by spawning a bundled binary | N — retired (#738) | Y (`cargo install`) | N — retired (#739) |
| Per-platform artifacts shipping the core | **1** (`.so` only) | 1 | **1** (addon only) |
| `dirsql server` + Ctrl-C exit code | 0 | 0 | 0 |
| `--version` reports the installed artifact's version (#958) | Y — the wheel's, via the pyo3 crate | Y — the core crate's | Y — `package.json`'s, read by the launcher |

**Parity restored.** All three languages now front the same `run_cli`: Rust is
the core, and both bindings call it in-process rather than spawning a copy.
Neither published package ships a standalone binary — `cargo install dirsql
--features cli` remains the way to get one, and it is the same code.

**#958 restored parity on the version string**, which had rendered the core
crate's `CARGO_PKG_VERSION` — a literal only the crates.io lane rewrites, so
both bindings reported `0.2.7` forever. The core gained
`cli::run_cli_with_version(argv, version)` and each binding passes the version
of the artifact it ships inside. The plumbing is asymmetric because the
manifest a publisher rewrites differs per lane: pyo3's `run_cli(argv)` bakes in
its own crate version (the constant it also exports as `__version__`), napi's
`runCli(argv, version?)` takes one. Both are launcher-facing, not SDK API.

#819 removed the per-query timeout from one-shot `dirsql query` in the core
(server mode's `query_timeout` / `408` pipeline is unchanged). This is a
CLI-lane behavior change, not an SDK-surface one: all three channels front the
Expand Down Expand Up @@ -556,7 +566,7 @@ both bindings against a real wheel / npm install.

| Test Scenario | Python (`tests/e2e/`) | Rust (`tests/`) | TypeScript (`tests/e2e/`) |
|----------------------------|--------|------|------------|
| `--version` exits 0 and prints the version | Y (`cli_version_test.py`) | Y (`cli_e2e.rs`) | Y (`internals/distcheck` `dirsql-distcheck node`, against the packed npm install) |
| `--version` exits 0 and prints the *installed artifact's* version (#958) | Y (`cli_version_test.py`; `dirsql-distcheck python` against the pip-installed wheel) | Y (`cli_e2e.rs`) | Y (`internals/distcheck` `dirsql-distcheck node`, against the packed npm install) |
| Launcher starts server; `POST /query` over HTTP | Y (`extension_package_test.py`) | Y | Y (`extension-package.test.ts`) |
| `[[dirsql.extension]]` package name resolved by the launcher (#227) | Y | N/A | Y |
| `interpret` subcommand removed; argv forwarded to clap (#321) | Y | core (clap dispatch) | Y |
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Config flags are subcommand-local: pass them after `server`
| `--persist [<path>]` | off | Keep the SQLite index on disk between runs so a restart only re-parses files that actually changed. Bare `--persist` caches at `<root>/.dirsql/cache.db`; `--persist <path>` caches at `<path>`. Off by default (the index is ephemeral). Also available on [`dirsql query`](#dirsql-query). See [Keep the index across restarts](../howto/persist.md). |
| `--no-ignore` | off | Scan files a `.gitignore` would hide. [Path-tables](./path-tables.md#skip-rules) respect `.gitignore` files by default; this flag restores the full walk. The built-in skips (`node_modules`/`.git`) and configured `ignore` patterns still apply. Also available on [`dirsql query`](#dirsql-query). |
| `--extension <path>` | none | Load a SQLite extension by literal path, overriding the config's `[[dirsql.extension]]` entries. Repeatable. Format: `<path>` or `<path>::<entrypoint>`. Internal plumbing for the pip/npm launchers, which resolve package-name extensions and pass the resolved paths here — not intended for direct use. When any `--extension` is present, the config file's own extension entries are not loaded. |
| `--version` | | Print the version and exit. |
| `--version` | | Print the version and exit. The version printed is that of the package you installed — the PyPI wheel, the npm package, or the crates.io binary. |
| `--help` | | Print usage and exit. |

### Defaults
Expand Down
18 changes: 16 additions & 2 deletions internals/distcheck/src/distcheck/node_flow/gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,22 @@ def run(
f"stdout={version.stdout!r}, stderr={version.stderr!r}"
)
_require_zero(version, version_err)
if "dirsql" not in version.stdout:
raise DistcheckError(version_err)

# Matching on the word "dirsql" alone let dirsql#958 ship: packages
# published at 0.4.x printed the embedded core crate's literal (0.2.7).
installed_main = json.loads(
fs.read_text(
os.path.join(install_root, "node_modules", "dirsql", "package.json")
)
)
installed_version = installed_main.get("version")
if version.stdout.strip() != f"dirsql {installed_version}":
raise DistcheckError(
f"`dirsql --version` printed {version.stdout.strip()!r}, expected "
f"'dirsql {installed_version}' -- the version npm installed. The "
"launcher (packages/ts/src/cli/main.ts) must read it from its own "
"package.json and pass it to the addon's `runCli`."
)

# 5. Cross-check the published layout: the launcher resolves to a real
# addon inside the installed `@dirsql/lib-<slug>` sub-package, and no
Expand Down
41 changes: 37 additions & 4 deletions internals/distcheck/src/distcheck/node_flow/gate_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,17 @@ class _Host:
_STAGED = "/ts/build/linux-x64-gnu/dirsql.linux-x64-gnu.node"
_BIN = "/inst/node_modules/.bin/dirsql"
_CLI_PKG = "/inst/node_modules/@dirsql/lib-linux-x64-gnu"
_MAIN_PKG_JSON = "/inst/node_modules/dirsql/package.json"
# The version npm installed -- `_MAIN_TGZ`'s, since that is the tarball
# installed above.
_INSTALLED = "0.0.1"


def _res(rc=0, stdout="", stderr=""):
return mock.Mock(returncode=rc, stdout=stdout, stderr=stderr)


def _fs(exists=True, read_name="@dirsql/lib-linux-x64-gnu"):
def _fs(exists=True, read_name="@dirsql/lib-linux-x64-gnu", installed=_INSTALLED):
fs = mock.Mock()
fs.mkdtemp.side_effect = ["/stg", "/inst"]
fs.listdir.side_effect = [[_CLI_TGZ], [_CLI_TGZ, _MAIN_TGZ]]
Expand All @@ -51,12 +55,18 @@ def _fs(exists=True, read_name="@dirsql/lib-linux-x64-gnu"):
# package is installed, which the gate now treats as a failure; the
# happy path is "everything present EXCEPT that".
fs.exists.side_effect = lambda p: exists and "@dirsql/cli-" not in p
fs.read_text.return_value = json.dumps({"name": read_name})
# Two installed manifests are read: the main package (for the version the
# CLI must report) and the addon sub-package (for its name).
fs.read_text.side_effect = lambda p: json.dumps(
{"version": installed} if p == _MAIN_PKG_JSON else {"name": read_name}
)
return fs


def _ok_sequence():
return [_res(), _res(), _res(), _res(stdout="dirsql 1.0")]
# The version stdout carries the trailing newline a real process emits, so
# the gate's `.strip()` is load-bearing rather than incidental.
return [_res(), _res(), _res(), _res(stdout=f"dirsql {_INSTALLED}\n")]


def test_select_tarball_finds_the_cli_tarball():
Expand Down Expand Up @@ -185,10 +195,33 @@ def test_run_version_nonzero_exit():
run("/ts", _HOST, runner=runner, fs=fs)


def test_run_version_disagrees_with_the_installed_package():
# dirsql#958: the CLI printed the core crate's literal, not the version
# npm installed. A "does it say dirsql" check passes that; this must not.
fs = _fs()
runner = mock.Mock(
side_effect=[_res(), _res(), _res(), _res(stdout="dirsql 0.2.7\n")]
)
with pytest.raises(DistcheckError, match="expected 'dirsql 0.0.1'"):
run("/ts", _HOST, runner=runner, fs=fs)


def test_run_version_older_than_the_installed_package():
# A stale version can sort either side of the installed one; both are a
# mismatch, and only testing one direction leaves an ordering comparison
# indistinguishable from the equality this asserts.
fs = _fs()
runner = mock.Mock(
side_effect=[_res(), _res(), _res(), _res(stdout="dirsql 0.0.0\n")]
)
with pytest.raises(DistcheckError, match="expected 'dirsql 0.0.1'"):
run("/ts", _HOST, runner=runner, fs=fs)


def test_run_version_missing_marker():
fs = _fs()
runner = mock.Mock(side_effect=[_res(), _res(), _res(), _res(stdout="nope")])
with pytest.raises(DistcheckError, match="--version` failed"):
with pytest.raises(DistcheckError, match="expected 'dirsql 0.0.1'"):
run("/ts", _HOST, runner=runner, fs=fs)


Expand Down
22 changes: 20 additions & 2 deletions internals/distcheck/src/distcheck/python_flow/gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ def sole_wheel(names) -> str:
return wheel


def wheel_version(wheel: str) -> str:
"""The version segment of a wheel filename (`dirsql-<ver>-<interp>-...`).

What pip actually installed -- the anchor the CLI and the SDK must both
agree with.
"""
return wheel.split("-")[1]


def check_wheel_tag(wheel: str) -> None:
"""Assert the stable-ABI (abi3) tag (#487): one `cp3x-abi3` wheel per
platform, not a version-locked `cpXY-cpXY` that re-inflates the release
Expand Down Expand Up @@ -101,6 +110,8 @@ def run(
)
_require_zero(install, f"pip install failed:\n{install.stderr}")

installed = wheel_version(wheel_name)

cli = os.path.join(venv_bin, "dirsql")
if not fs.exists(cli):
raise DistcheckError(f"console script missing at {cli}")
Expand All @@ -115,8 +126,15 @@ def run(
f"stdout={version.stdout!r}, stderr={version.stderr!r}"
)
_require_zero(version, version_err)
if "dirsql" not in version.stdout:
raise DistcheckError(version_err)
# Matching on the word "dirsql" alone let dirsql#958 ship: wheels
# published at 0.4.x printed the embedded core crate's literal (0.2.7).
if version.stdout.strip() != f"dirsql {installed}":
raise DistcheckError(
f"`dirsql --version` printed {version.stdout.strip()!r}, expected "
f"'dirsql {installed}' -- the version pip installed. Have "
"`run_cli` in packages/python/src/lib.rs pass this crate's "
"`CARGO_PKG_VERSION` to `dirsql::cli::run_cli_with_version`."
)

# cwd is the scratch staging dir so `import dirsql` resolves the
# installed wheel, never the source tree.
Expand Down
28 changes: 24 additions & 4 deletions internals/distcheck/src/distcheck/python_flow/gate_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
check_wheel_tag,
run,
sole_wheel,
wheel_version,
)

_WHEEL = "dirsql-1.0-cp311-abi3-linux_x86_64.whl"
Expand All @@ -37,11 +38,13 @@ def _fs(staging="/stg", listdir=None, exists=True):


def _ok_sequence():
# Both version stdouts carry the trailing newline a real process emits, so
# the gate's `.strip()` is load-bearing rather than incidental.
return [
_res(), # maturin build
_res(), # venv
_res(), # pip install
_res(stdout="dirsql 1.0"), # --version
_res(stdout="dirsql 1.0\n"), # --version, matching _WHEEL's version
_res(stdout="1.0\n"), # import dirsql
]

Expand Down Expand Up @@ -79,6 +82,10 @@ def test_sole_wheel_rejects_many():
sole_wheel(["a.whl", "b.whl"])


def test_wheel_version_reads_the_version_segment():
assert wheel_version(_WHEEL) == "1.0"


def test_check_wheel_tag_accepts_abi3_cpython():
check_wheel_tag(_WHEEL) # no raise

Expand Down Expand Up @@ -172,17 +179,30 @@ def test_run_version_nonzero_exit():
run("/pkg", "/repo", runner=runner, fs=fs)


def test_run_version_disagrees_with_the_installed_wheel():
# dirsql#958: the CLI printed the core crate's literal, not the version pip
# installed. A "does it say dirsql" check passes that; this must not. Both
# orderings, so an ordering comparison cannot pass for the equality.
fs = _fs()
for printed in ("dirsql 0.2.7\n", "dirsql 2.0\n"):
runner = mock.Mock(
side_effect=[_res(), _res(), _res(), _res(stdout=printed)]
)
with pytest.raises(DistcheckError, match="expected 'dirsql 1.0'"):
run("/pkg", "/repo", runner=runner, fs=_fs())


def test_run_version_missing_marker():
fs = _fs()
runner = mock.Mock(side_effect=[_res(), _res(), _res(), _res(stdout="nope")])
with pytest.raises(DistcheckError, match="--version` failed"):
with pytest.raises(DistcheckError, match="expected 'dirsql 1.0'"):
run("/pkg", "/repo", runner=runner, fs=fs)


def test_run_import_nonzero_exit():
fs = _fs()
runner = mock.Mock(
side_effect=[_res(), _res(), _res(), _res(stdout="dirsql 1.0"), _res(rc=1)]
side_effect=[_res(), _res(), _res(), _res(stdout="dirsql 1.0\n"), _res(rc=1)]
)
with pytest.raises(DistcheckError, match="import dirsql` failed"):
run("/pkg", "/repo", runner=runner, fs=fs)
Expand All @@ -195,7 +215,7 @@ def test_run_import_empty_stdout():
_res(),
_res(),
_res(),
_res(stdout="dirsql 1.0"),
_res(stdout="dirsql 1.0\n"),
_res(stdout=" \n"),
]
)
Expand Down
9 changes: 6 additions & 3 deletions packages/python/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
name = "dirsql-py-ext"
# Explicit literal (not `version.workspace = true`) so putitoutthere's
# pypi/maturin handler can rewrite it via `write-version` before
# `maturin build`. `pyproject.toml` declares `dynamic = ["version"]`
# and maturin reads this field. Mirrors `packages/rust/Cargo.toml`.
version = "0.2.7"
# `maturin build`. `pyproject.toml` declares `dynamic = ["version"]` and
# maturin reads this field, so it is the wheel's version -- and, since #958,
# the version the CLI prints. It must NOT equal `packages/rust/Cargo.toml`'s
# literal: with the two equal, `dirsql-distcheck python` passes even when the
# CLI is reading the core crate's version, which is how #958 shipped.
version = "0.0.1"
# abi3 note: the `abi3-py310` pyo3 feature (below) builds ONE stable-ABI
# `cp310-abi3` wheel per platform that loads on every CPython >= 3.10
# (matching `pyproject.toml`'s `requires-python = ">=3.10"`), instead of
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
**Fixed** — `dirsql --version` reports the installed wheel's version instead of
a frozen `0.2.7`: it read the embedded core crate's literal, which only the
crates.io release lane rewrites, so `uvx dirsql@0.4.20 --version` answered
`dirsql 0.2.7` (#958).
7 changes: 7 additions & 0 deletions packages/python/e2e-attestations/claude-issue-958-q2loh3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"command": "uv run python -m pytest tests/e2e/ -x -q",
"ran_at": 1787249780,
"exit_code": 0,
"commit": "b1bdc43417b1b170009b720482bd36623c6743e3",
"branch": "claude/issue-958-q2loh3"
}
13 changes: 11 additions & 2 deletions packages/python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,15 @@ mod python {
}
}

/// This wheel's version: `packages/python/Cargo.toml`'s `[package].version`,
/// which maturin writes into the wheel metadata and the release tooling
/// rewrites to the published version at build time.
///
/// The CLI and `__version__` both read it, so they cannot disagree — and
/// neither reports the embedded core crate's literal, which no release
/// lane but crates.io rewrites (#958).
const VERSION: &str = env!("CARGO_PKG_VERSION");

/// Run the `dirsql` CLI in this process and return its exit code.
///
/// The console script calls this instead of exec'ing a bundled binary, so
Expand All @@ -463,13 +472,13 @@ mod python {
let mut full = Vec::with_capacity(argv.len() + 1);
full.push("dirsql".to_string());
full.extend(argv);
py.detach(|| dirsql::cli::run_cli(full))
py.detach(|| dirsql::cli::run_cli_with_version(full, VERSION))
}

#[pymodule]
#[pyo3(name = "_dirsql")]
fn py_dirsql_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
m.add("__version__", VERSION)?;
m.add_class::<PyTable>()?;
m.add_class::<PyDirSQL>()?;
m.add_class::<PyRowEvent>()?;
Expand Down
Loading
Loading