Skip to content
Open
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
16 changes: 15 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,27 @@ jobs:
- name: "install dependencies"
run: sudo apt install -y cmake ninja-build catch2 unixodbc-dev sqlite3 libsqlite3-dev libsqliteodbc uuid-dev libyaml-cpp-dev gcc-14 libtbb-dev libzip-dev
- name: "cmake"
# ENABLE_TIDY=OFF: this job invokes `run-clang-tidy` directly after
# configure; we don't want CMAKE_CXX_CLANG_TIDY also wired up
# because that runs clang-tidy on libunicode's own sources during
# the codegen build step below — and libunicode predates clang-tidy
# 22's pedantic warning set.
run: |
cmake --preset clang-debug \
-D CMAKE_CXX_COMPILER=clang++-${{ env.CLANG_TOOLS_VERSION }} \
-D CMAKE_C_COMPILER=clang-${{ env.CLANG_TOOLS_VERSION }} \
-D ENABLE_TIDY=OFF \
-B build
- name: "build libunicode codegen (UCD tables)"
# `unicode_ucd` is the libunicode static library whose source list
# includes the auto-generated `ucd_enums.h` and friends. Building it
# (only it) materialises those headers so that clang-tidy can parse
# `<libunicode/codepoint_properties.h>` transitively included from
# our vendored `tui/MarkdownTable.cpp`. Without this step, clang-tidy
# aborts with `'libunicode/ucd_enums.h' file not found`.
run: cmake --build build --target unicode_ucd
- name: "run clang-tidy"
run: run-clang-tidy-${{ env.CLANG_TOOLS_VERSION }} -p ./build -clang-tidy-binary clang-tidy-${{ env.CLANG_TOOLS_VERSION }} -config-file .clang-tidy -header-filter='^(?!(.*/)?stdexec-src/|(.*/)?libzip-src/).*$' -source-filter='^(?!.*_deps/).*$'
run: run-clang-tidy-${{ env.CLANG_TOOLS_VERSION }} -p ./build -clang-tidy-binary clang-tidy-${{ env.CLANG_TOOLS_VERSION }} -config-file .clang-tidy -header-filter='^(?!(.*/)?stdexec-src/|(.*/)?libzip-src/|(.*/)?libunicode-src/).*$' -source-filter='^(?!.*_deps/).*$'

# }}}
# {{{ Windows
Expand Down
33 changes: 33 additions & 0 deletions cmake/FindOrFetchLibunicode.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# SPDX-License-Identifier: Apache-2.0
#
# Locates libunicode — first via find_package, falling back to CPM/FetchContent
# from upstream. Defines the imported target `unicode::unicode` (or, on older
# packagings, `unicode::core`) that callers can link against.
#
# Pinned to the same version as endo's `tui` module so the vendored
# `MarkdownTable.cpp` keeps compiling against a known-good API.

set(LIBUNICODE_REQUIRED_VERSION "0.9.0")

if(NOT TARGET unicode::unicode AND NOT TARGET unicode::core)
find_package(libunicode ${LIBUNICODE_REQUIRED_VERSION} QUIET)
endif()

if(TARGET unicode::unicode OR TARGET unicode::core)
message(STATUS "libunicode: using system package")
else()
message(STATUS "libunicode: not found, fetching v${LIBUNICODE_REQUIRED_VERSION} via CPM")
CPMAddPackage(
NAME libunicode
GITHUB_REPOSITORY contour-terminal/libunicode
GIT_TAG v${LIBUNICODE_REQUIRED_VERSION}
OPTIONS
"LIBUNICODE_TESTING OFF"
"LIBUNICODE_BENCHMARK OFF"
"LIBUNICODE_TOOLS OFF"
"LIBUNICODE_EXAMPLES OFF"
"BUILD_SHARED_LIBS OFF"
EXCLUDE_FROM_ALL YES
SYSTEM YES
)
endif()
57 changes: 57 additions & 0 deletions docs/dbtool.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,63 @@ Useful for:
- Baseline migrations when setting up an existing database
- Skipping migrations that were applied manually

## Diff

### diff

Compares two databases and prints a colored, structured report of their differences.
Default mode reports both **schema** (tables, columns, types, primary keys, foreign
keys, indexes) and **data** (per-row diffs paired by primary key); `--schema-only`
suppresses the data phase.

```
dbtool diff <SOURCE-A> <SOURCE-B> [--schema-only] [--no-color] [--max-rows N]
```

Each `<SOURCE>` is either:

- a **profile name** from the dbtool config file (resolved like `--profile`), or
- a **raw ODBC connection string** starting with `DRIVER=...` (case-insensitive).

Live progress lines (current table, rows scanned, smoothed-rate ETA) are emitted to
stderr while the diff runs; the final report is printed to stdout once progress
completes. `--quiet` and `--progress logline|ascii|unicode` apply as elsewhere.

`diff` is fully **read-only** — only `SELECT` queries are issued against either side.

Exit codes:

- `0` — databases are equivalent under the chosen mode
- `1` — differences were found
- `2` — error (connection failure, profile not found, etc.)

#### Examples

```bash
# Compare two profiles defined in your config:
dbtool diff prod staging

# Compare a profile against an SQLite snapshot:
dbtool diff prod "DRIVER=SQLite3;Database=/tmp/snapshot.db"

# Schema-only check (fast — useful for CI gating):
dbtool diff prod staging --schema-only

# Cap data scan to the first 10k rows per table:
dbtool diff prod staging --max-rows 10000
```

#### Caveats

- Tables without a primary key are reported under the schema diff but **skipped**
for the data diff (with a `(skipped: no primary key)` note). PK pairing is
required to distinguish a *changed* row from an *added + removed* pair.
- Column values are compared as their ODBC string representation. This may produce
false positives for floating-point or binary columns whose textual encoding
differs across drivers; rerun with `--schema-only` to confirm if the column's
type matches.
- `--max-rows` truncation is per-side; the report flags `truncated` when hit.

## Backup & Restore

### backup
Expand Down
41 changes: 41 additions & 0 deletions src/Lightweight/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,20 @@ set(HEADER_FILES
SqlMigration.hpp
SqlOdbcWide.hpp
SqlQueryFormatter.hpp
SqlDataDiff.hpp
SqlSchema.hpp
SqlSchemaDiff.hpp
SqlScopedLock.hpp
SqlScopedTraceLogger.hpp
SqlServerType.hpp
SqlStatement.hpp
TracyProfiler.hpp

tui/MarkdownInline.hpp
tui/MarkdownTable.hpp
tui/SgrBuilder.hpp
tui/Style.hpp
tui/Unicode.hpp
)

set(SOURCE_FILES
Expand Down Expand Up @@ -131,7 +139,9 @@ set(SOURCE_FILES

SqlQueryFormatter.cpp
SqlScopedLock.cpp
SqlDataDiff.cpp
SqlSchema.cpp
SqlSchemaDiff.cpp
SqlStatement.cpp
SqlTransaction.cpp
Utils.cpp
Expand Down Expand Up @@ -175,14 +185,45 @@ target_sources(LightweightTools PRIVATE
)
target_link_libraries(LightweightTools PUBLIC Lightweight)

# LightweightTui — vendored terminal/markdown rendering helpers (tui/).
# Kept as a separate static library so its symbols don't need to cross the
# Lightweight DLL ABI on Windows. libunicode stays a private detail.
add_library(LightweightTui STATIC
tui/MarkdownInline.hpp
tui/MarkdownTable.cpp
tui/MarkdownTable.hpp
tui/SgrBuilder.cpp
tui/SgrBuilder.hpp
tui/Style.hpp
tui/Unicode.cpp
tui/Unicode.hpp
)
add_library(Lightweight::Tui ALIAS LightweightTui)
target_compile_features(LightweightTui PUBLIC cxx_std_23)
target_include_directories(LightweightTui PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>
)
if(NOT WIN32)
set_target_properties(LightweightTui PROPERTIES POSITION_INDEPENDENT_CODE ON)
endif()

include("${CMAKE_SOURCE_DIR}/cmake/FindOrFetchLibunicode.cmake")
if(TARGET unicode::unicode)
target_link_libraries(LightweightTui PRIVATE unicode::unicode)
elseif(TARGET unicode::core)
target_link_libraries(LightweightTui PRIVATE unicode::core)
endif()

if(CLANG_TIDY_EXE)
set_target_properties(Lightweight PROPERTIES CXX_CLANG_TIDY "${CLANG_TIDY_EXE}")
set_target_properties(LightweightTools PROPERTIES CXX_CLANG_TIDY "${CLANG_TIDY_EXE}")
set_target_properties(LightweightTui PROPERTIES CXX_CLANG_TIDY "${CLANG_TIDY_EXE}")
endif()

# Enable coverage instrumentation for library targets
enable_coverage_for_target(Lightweight)
enable_coverage_for_target(LightweightTools)
enable_coverage_for_target(LightweightTui)

target_include_directories(Lightweight PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>
Expand Down
16 changes: 10 additions & 6 deletions src/Lightweight/QueryFormatter/SQLiteFormatter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -498,23 +498,27 @@ class SQLiteQueryFormatter: public SqlQueryFormatter
// commas so the split-on-',' parse on the runtime side is unambiguous.
auto const joinComma = [](std::vector<std::string> const& v) {
std::string out;
for (size_t i = 0; i < v.size(); ++i)
bool first = true;
for (auto const& s: v)
{
if (i != 0)
if (!first)
out += ',';
out += v[i];
out += s;
first = false;
}
return out;
};
auto const joinQuoted = [](std::vector<std::string> const& v) {
std::string out;
for (size_t i = 0; i < v.size(); ++i)
bool first = true;
for (auto const& s: v)
{
if (i != 0)
if (!first)
out += ", ";
out += '"';
out += v[i];
out += s;
out += '"';
first = false;
}
return out;
};
Expand Down
Loading
Loading