feat: add capture and asset provenance v1

This commit is contained in:
fy59 2026-08-27 07:55:33 +02:00
parent 2ab4487f1e
commit 9f409645e3
13 changed files with 831 additions and 48 deletions

View file

@ -25,11 +25,14 @@ persistante, enrichissable et versionnable.
- **Project** : cycle de vie persistant, identité stable et Project Database ouverte - **Project** : cycle de vie persistant, identité stable et Project Database ouverte
- **Import** : premier task kind de production, exécuté par la file générique en lots bornés et reprenables - **Import** : premier task kind de production, exécuté par la file générique en lots bornés et reprenables
- **ScanSet / Image Catalog v1** : acquisitions, images logiques, provenance et assets SHA-256 persistants et paginés - **ScanSet / Image Catalog v1** : acquisitions, images logiques, provenance et assets SHA-256 persistants et paginés
- **Capture / Asset Provenance v1** : Captures par ScanSet, associations source/dérivé
et sélection explicite d'une image logique — IMPLEMENTED / VALIDATION PENDING
- **Feature Store v1/v2** : ORB U8×32, SIFT/RootSIFT F32×128 et lecture typée bornée - **Feature Store v1/v2** : ORB U8×32, SIFT/RootSIFT F32×128 et lecture typée bornée
- **Image View** : vues triées et filtrées pour la TUI - **Image View** : vues triées et filtrées pour la TUI
- **Task** : moteur de tâches avec pause/reprise, annulation et séquences - **Task** : moteur de tâches avec pause/reprise, annulation et séquences
- **Task Checkpoint v1** : snapshot durable, fichier atomique et reprise sûre - **Task Checkpoint v1** : snapshot durable, fichier atomique et reprise sûre
- **Project Database v18** : résultats v16 et payload durable typé Sparse SfM Gate F - **Project Database v19** : fondations v18 préservées, avec Capture / Asset
Provenance additive
- **Sparse SfM Gates C/D/E** : géométrie calibrée, noyau incrémental et Bundle - **Sparse SfM Gates C/D/E** : géométrie calibrée, noyau incrémental et Bundle
Adjustment final par composante, tous PASS / FROZEN Adjustment final par composante, tous PASS / FROZEN
- **Sparse SfM Gate F** : orchestration durable, runtime gouverné et publication - **Sparse SfM Gate F** : orchestration durable, runtime gouverné et publication

View file

@ -1,5 +1,24 @@
# Base de données projet Lardon3D # Base de données projet Lardon3D
## Capture / Asset Provenance v1 — Project DB v19
**IMPLEMENTED / VALIDATION PENDING.** La migration transactionnelle v18→v19
ajoute seulement une fondation de catalogue : `captures`, `capture_images`,
`capture_assets`, `capture_selections` et `asset_derivations`. Un `Capture`
appartient à un seul ScanSet ; il n'est ni un `image_id` ni une nouvelle identité
scientifique. Les assets source et dérivés peuvent lui être associés sans créer
d'image logique. Une sélection courante optionnelle référence exactement une
image déjà associée au Capture et ne modifie jamais cette image, son asset, ni
les résultats scientifiques existants.
`asset_derivations` est volontairement limité à un parent asset et un enfant
asset, avec kind/version et fingerprint canonique de 32 octets. Il n'est pas un
DAG générique et ne réalise aucun développement RAW ni extraction vidéo. La
migration crée, par `image_id` croissant, un Capture legacy indépendant pour
chaque image v18, relie son asset comme source et rend cette même image
sélectionnée. Aucun nom de fichier, EXIF ou rapprochement de siblings n'est
interprété ; les IDs et la sémantique v18 restent inchangés.
## Phase H — décision Project DB v18 ## Phase H — décision Project DB v18
**PASS / FROZEN.** La migration transactionnelle v17→v18 **PASS / FROZEN.** La migration transactionnelle v17→v18
@ -65,7 +84,9 @@ persistée exactement ; seule une composante de graphe non reconstruite est
omise. Les comptes et métriques globaux décrivent exclusivement la géométrie omise. Les comptes et métriques globaux décrivent exclusivement la géométrie
effectivement persistée. Aucun placeholder ni table diagnostique n'est ajouté. effectivement persistée. Aucun placeholder ni table diagnostique n'est ajouté.
> Version courante : **v18**. La migration transactionnelle v17→v18 ajoute le > Version courante : **v19**. La migration transactionnelle v18→v19 ajoute la
> fondation Capture/Asset Provenance décrite ci-dessus sans modifier les tables
> scientifiques historiques. La migration transactionnelle v17→v18 ajoute le
> discriminateur générique nullable et les deux tables H minimales décrites > discriminateur générique nullable et les deux tables H minimales décrites
> ci-dessus. La migration v16→v17 ajoute le > ci-dessus. La migration v16→v17 ajoute le
> payload durable typé `sparse_sfm_tasks`. La migration transactionnelle v15→v16 ajoute > payload durable typé `sparse_sfm_tasks`. La migration transactionnelle v15→v16 ajoute
@ -808,8 +829,8 @@ ouvert.
## Statut ## Statut
**IMPLEMENTED** — SQLite système, schéma v18 et migrations séquentielles **IMPLEMENTED / VALIDATION PENDING** — SQLite système, schéma v19 et migrations séquentielles
v1→v2→v3→v4→v5→v6→v7→v8→v9→v10→v11→v12→v13→v14→v15→v16→v17→v18, identité projet, transactions v1→v2→v3→v4→v5→v6→v7→v8→v9→v10→v11→v12→v13→v14→v15→v16→v17→v18→v19, identité projet, transactions
tâche+checkpoint, pagination de reprise et artefacts génériques. tâche+checkpoint, pagination de reprise et artefacts génériques.
**IMPLEMENTED** — ouverture/fermeture avec le projet, identité INI/DB cohérente, **IMPLEMENTED** — ouverture/fermeture avec le projet, identité INI/DB cohérente,
@ -829,6 +850,12 @@ bornée à 256. Les identités sont des `INTEGER PRIMARY KEY AUTOINCREMENT` SQLi
allouées sous transaction ; aucun `SELECT MAX()+1` n'est utilisé en allouées sous transaction ; aucun `SELECT MAX()+1` n'est utilisé en
fonctionnement normal et une identité validée n'est jamais réutilisée. fonctionnement normal et une identité validée n'est jamais réutilisée.
**IMPLEMENTED / VALIDATION PENDING** — Capture / Asset Provenance v1 :
Capture borné par ScanSet, associations explicites asset/image, sélection
courante optionnelle et dérivation asset à parent unique. RAW, vidéo,
appariement automatique et toute exécution de dérivation restent hors de cette
fondation.
La migration v3→v4 ne lit pas `manifest.tsv`. Elle crée le ScanSet legacy et La migration v3→v4 ne lit pas `manifest.tsv`. Elle crée le ScanSet legacy et
positionne `metadata.legacy_image_catalog_pending=1` dès qu'une ancienne tâche positionne `metadata.legacy_image_catalog_pending=1` dès qu'une ancienne tâche
d'import existe. Cet indicateur signifie « données legacy potentiellement non d'import existe. Cet indicateur signifie « données legacy potentiellement non

View file

@ -13,8 +13,10 @@ l'import.
```text ```text
Project Project
└── ScanSet (acquisition logique) └── ScanSet (acquisition logique)
└── Image (observation logique) └── Capture (observation physique potentielle)
└── Image Asset (contenu physique géré) ├── Image Assets source/dérivés (contenus physiques gérés)
├── Images (représentations logiques)
└── zéro ou une Image sélectionnée
``` ```
- `scanset_id` identifie durablement une acquisition, indépendamment de son - `scanset_id` identifie durablement une acquisition, indépendamment de son
@ -25,6 +27,15 @@ Project
chemin relatif décrivent le fichier géré par Lardon3D. chemin relatif décrivent le fichier géré par Lardon3D.
- La provenance d'une image conserve son nom original, son chemin source, son - La provenance d'une image conserve son nom original, son chemin source, son
instant d'import et, s'il existe, le `task_id` importeur. instant d'import et, s'il existe, le `task_id` importeur.
- Un `Capture` est une association de catalogue dans un unique ScanSet ; il ne
remplace pas `image_id`. Il peut conserver par exemple les assets RAW et JPEG
d'un même déclenchement, sans inférer ce lien à partir de leurs noms.
- Seule une image logique explicitement sélectionnée est la représentation
courante proposée au pipeline scientifique ultérieur. La sélection ne modifie
ni l'image, ni son asset, ni les résultats existants.
- Une dérivation asset bornée parent→enfant, versionnée par fingerprint, rend
possible une future image de travail RAW ou frame vidéo sans prétendre que
RAW ou vidéo sont déjà importés.
Un contenu identique n'implique pas une identité logique unique. Dans un même Un contenu identique n'implique pas une identité logique unique. Dans un même
ScanSet, le couple `(scanset_id, asset_id)` est unique : réimporter le même ScanSet, le couple `(scanset_id, asset_id)` est unique : réimporter le même

View file

@ -58,6 +58,10 @@ Lardon3D suit une feuille de route ordonnée qui privilégie la stabilité et la
### Phase 5 : Reconstruction ### Phase 5 : Reconstruction
- ✅ Orchestration de reconstruction incrémentale H v1 — **PASS / FROZEN** - ✅ Orchestration de reconstruction incrémentale H v1 — **PASS / FROZEN**
- 🔄 Capture / Asset Provenance v1 — **IMPLEMENTED / VALIDATION PENDING** :
Project DB v19 ajoute les Captures par ScanSet, les associations source/dérivé
et la sélection explicite d'une image logique, sans changer le pipeline
scientifique. L'ingestion RAW et vidéo reste PLANNED.
- ✅ MVS-M1 — **PASS / FROZEN** : identité dense `L3DMDID2` - ✅ MVS-M1 — **PASS / FROZEN** : identité dense `L3DMDID2`
v2 (220 octets) liant reconstruction de base, jeu d'images source, v2 (220 octets) liant reconstruction de base, jeu d'images source,
`calibration_scope_identity` historique, binding numérique exact `L3DMCAL1` v1, `calibration_scope_identity` historique, binding numérique exact `L3DMCAL1` v1,

View file

@ -10,7 +10,7 @@
#include <lardon3d/sparse_sfm_incremental.h> #include <lardon3d/sparse_sfm_incremental.h>
enum { enum {
LARDON3D_PROJECT_DB_SCHEMA_VERSION = 18, LARDON3D_PROJECT_DB_SCHEMA_VERSION = 19,
LARDON3D_PROJECT_DB_ID_CAPACITY = 65, LARDON3D_PROJECT_DB_ID_CAPACITY = 65,
LARDON3D_PROJECT_DB_KIND_CAPACITY = 65, LARDON3D_PROJECT_DB_KIND_CAPACITY = 65,
LARDON3D_PROJECT_DB_PATH_CAPACITY = 4096, LARDON3D_PROJECT_DB_PATH_CAPACITY = 4096,
@ -129,6 +129,38 @@ typedef struct {
int64_t imported_at; int64_t imported_at;
} Lardon3DProjectDbImage; } Lardon3DProjectDbImage;
typedef struct {
uint64_t capture_id;
uint64_t scanset_id;
int64_t created_at;
} Lardon3DProjectDbCapture;
typedef enum {
LARDON3D_DB_CAPTURE_ASSET_SOURCE = 1,
LARDON3D_DB_CAPTURE_ASSET_DERIVED = 2,
} Lardon3DProjectDbCaptureAssetRole;
typedef struct {
uint64_t capture_id;
uint64_t asset_id;
Lardon3DProjectDbCaptureAssetRole role;
} Lardon3DProjectDbCaptureAsset;
typedef enum {
LARDON3D_DB_ASSET_DERIVATION_GENERIC_VERSIONED = 1,
} Lardon3DProjectDbAssetDerivationKind;
typedef struct {
uint64_t parent_asset_id;
uint64_t child_asset_id;
Lardon3DProjectDbAssetDerivationKind kind;
uint32_t version;
unsigned char parameter_fingerprint[LARDON3D_PROJECT_DB_SHA256_SIZE];
bool has_producer_task;
uint64_t producer_task_id;
int64_t created_at;
} Lardon3DProjectDbAssetDerivation;
typedef struct { typedef struct {
uint64_t candidate_pair_id; uint64_t candidate_pair_id;
uint64_t image_id_a; uint64_t image_id_a;
@ -469,6 +501,10 @@ Lardon3DProjectDbResult lardon3d_project_db_register_image(
uint64_t size_bytes, const char *original_name, const char *source_path, uint64_t size_bytes, const char *original_name, const char *source_path,
uint64_t producer_task_id, int64_t imported_at, Lardon3DProjectDbImageRegisterStatus *status, uint64_t producer_task_id, int64_t imported_at, Lardon3DProjectDbImageRegisterStatus *status,
Lardon3DProjectDbImage *image); Lardon3DProjectDbImage *image);
Lardon3DProjectDbResult lardon3d_project_db_register_image_asset(
Lardon3DProjectDb *database,
const unsigned char sha256[LARDON3D_PROJECT_DB_SHA256_SIZE], const char *asset_path,
uint64_t size_bytes, int64_t created_at, Lardon3DProjectDbImageAsset *asset);
Lardon3DProjectDbResult lardon3d_project_db_load_image(Lardon3DProjectDb *database, Lardon3DProjectDbResult lardon3d_project_db_load_image(Lardon3DProjectDb *database,
uint64_t image_id, uint64_t image_id,
Lardon3DProjectDbImage *image, Lardon3DProjectDbImage *image,
@ -481,6 +517,33 @@ Lardon3DProjectDbResult lardon3d_project_db_list_images(Lardon3DProjectDb *datab
size_t capacity, size_t *count); size_t capacity, size_t *count);
Lardon3DProjectDbResult lardon3d_project_db_count_images(Lardon3DProjectDb *database, Lardon3DProjectDbResult lardon3d_project_db_count_images(Lardon3DProjectDb *database,
uint64_t scanset_id, uint64_t *count); uint64_t scanset_id, uint64_t *count);
Lardon3DProjectDbResult lardon3d_project_db_create_capture(
Lardon3DProjectDb *database, uint64_t scanset_id, int64_t created_at,
Lardon3DProjectDbCapture *capture);
Lardon3DProjectDbResult lardon3d_project_db_load_capture(
Lardon3DProjectDb *database, uint64_t capture_id, Lardon3DProjectDbCapture *capture);
Lardon3DProjectDbResult lardon3d_project_db_list_captures(
Lardon3DProjectDb *database, uint64_t scanset_id, uint64_t after_capture_id,
Lardon3DProjectDbCapture *captures, size_t capacity, size_t *count);
Lardon3DProjectDbResult lardon3d_project_db_attach_capture_asset(
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t asset_id,
Lardon3DProjectDbCaptureAssetRole role);
Lardon3DProjectDbResult lardon3d_project_db_list_capture_assets(
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t after_asset_id,
Lardon3DProjectDbCaptureAsset *assets, size_t capacity, size_t *count);
Lardon3DProjectDbResult lardon3d_project_db_attach_capture_image(
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t image_id);
Lardon3DProjectDbResult lardon3d_project_db_find_capture_for_image(
Lardon3DProjectDb *database, uint64_t image_id, Lardon3DProjectDbCapture *capture);
Lardon3DProjectDbResult lardon3d_project_db_record_asset_derivation(
Lardon3DProjectDb *database, const Lardon3DProjectDbAssetDerivation *derivation);
Lardon3DProjectDbResult lardon3d_project_db_load_asset_derivation(
Lardon3DProjectDb *database, uint64_t child_asset_id,
Lardon3DProjectDbAssetDerivation *derivation);
Lardon3DProjectDbResult lardon3d_project_db_set_selected_capture_image(
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t image_id);
Lardon3DProjectDbResult lardon3d_project_db_get_selected_capture_image(
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t *image_id);
Lardon3DProjectDbResult lardon3d_project_db_create_candidate_pair( Lardon3DProjectDbResult lardon3d_project_db_create_candidate_pair(
Lardon3DProjectDb *database, uint64_t image_id_a, uint64_t image_id_b, int64_t created_at, Lardon3DProjectDb *database, uint64_t image_id_a, uint64_t image_id_b, int64_t created_at,
Lardon3DProjectDbCandidatePair *pair); Lardon3DProjectDbCandidatePair *pair);

View file

@ -577,6 +577,36 @@ static const char schema_incremental_reconstruction_v18[] =
"incremental_version INTEGER NOT NULL CHECK(incremental_version=1)," "incremental_version INTEGER NOT NULL CHECK(incremental_version=1),"
"parameter_fingerprint BLOB NOT NULL CHECK(length(parameter_fingerprint)=32));"; "parameter_fingerprint BLOB NOT NULL CHECK(length(parameter_fingerprint)=32));";
static const char schema_capture_provenance_v19[] =
"CREATE TABLE captures("
"capture_id INTEGER PRIMARY KEY AUTOINCREMENT CHECK(capture_id>0),"
"scanset_id INTEGER NOT NULL REFERENCES scansets(scanset_id),"
"created_at INTEGER NOT NULL CHECK(created_at>=0));"
"CREATE INDEX captures_scanset_idx ON captures(scanset_id,capture_id);"
"CREATE TABLE capture_images("
"capture_id INTEGER NOT NULL REFERENCES captures(capture_id) ON DELETE CASCADE,"
"image_id INTEGER NOT NULL UNIQUE REFERENCES images(image_id),"
"PRIMARY KEY(capture_id,image_id));"
"CREATE TABLE capture_assets("
"capture_id INTEGER NOT NULL REFERENCES captures(capture_id) ON DELETE CASCADE,"
"asset_id INTEGER NOT NULL REFERENCES image_assets(asset_id),"
"role INTEGER NOT NULL CHECK(role IN(1,2)),"
"PRIMARY KEY(capture_id,asset_id));"
"CREATE INDEX capture_assets_page_idx ON capture_assets(capture_id,asset_id);"
"CREATE TABLE capture_selections("
"capture_id INTEGER PRIMARY KEY REFERENCES captures(capture_id) ON DELETE CASCADE,"
"image_id INTEGER NOT NULL UNIQUE REFERENCES images(image_id),"
"FOREIGN KEY(capture_id,image_id) REFERENCES capture_images(capture_id,image_id));"
"CREATE TABLE asset_derivations("
"child_asset_id INTEGER PRIMARY KEY REFERENCES image_assets(asset_id),"
"parent_asset_id INTEGER NOT NULL REFERENCES image_assets(asset_id),"
"derivation_kind INTEGER NOT NULL CHECK(derivation_kind=1),"
"derivation_version INTEGER NOT NULL CHECK(derivation_version>0),"
"parameter_fingerprint BLOB NOT NULL CHECK(length(parameter_fingerprint)=32),"
"producer_task_id INTEGER REFERENCES tasks(task_id),"
"created_at INTEGER NOT NULL CHECK(created_at>=0),"
"CHECK(parent_asset_id!=child_asset_id));";
static void copy_error(char destination[LARDON3D_PROJECT_DB_ERROR_CAPACITY], const char *text) { static void copy_error(char destination[LARDON3D_PROJECT_DB_ERROR_CAPACITY], const char *text) {
if (destination) { if (destination) {
(void)snprintf(destination, LARDON3D_PROJECT_DB_ERROR_CAPACITY, "%s", text ? text : ""); (void)snprintf(destination, LARDON3D_PROJECT_DB_ERROR_CAPACITY, "%s", text ? text : "");
@ -668,7 +698,7 @@ static Lardon3DProjectDbResult migrate(Lardon3DProjectDb *database, unsigned int
from_version != 4 && from_version != 5 && from_version != 6 && from_version != 7 && from_version != 4 && from_version != 5 && from_version != 6 && from_version != 7 &&
from_version != 8 && from_version != 9 && from_version != 10 && from_version != 11 && from_version != 8 && from_version != 9 && from_version != 10 && from_version != 11 &&
from_version != 12 && from_version != 13 && from_version != 14 && from_version != 12 && from_version != 13 && from_version != 14 &&
from_version != 15 && from_version != 16 && from_version != 17) { from_version != 15 && from_version != 16 && from_version != 17 && from_version != 18) {
return LARDON3D_PROJECT_DB_CORRUPT; return LARDON3D_PROJECT_DB_CORRUPT;
} }
Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin migration"); Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin migration");
@ -1033,6 +1063,36 @@ static Lardon3DProjectDbResult migrate(Lardon3DProjectDb *database, unsigned int
"finish schema v18 migration"); "finish schema v18 migration");
} }
} }
if (result == LARDON3D_PROJECT_DB_OK && from_version < 19) {
result = execute(database, schema_capture_provenance_v19,
"migrate image catalog capture provenance v18 to v19");
if (result == LARDON3D_PROJECT_DB_OK) {
result = execute(
database,
"INSERT INTO captures(capture_id,scanset_id,created_at) "
"SELECT image_id,scanset_id,imported_at FROM images ORDER BY image_id;"
"INSERT INTO capture_images(capture_id,image_id) "
"SELECT image_id,image_id FROM images ORDER BY image_id;"
"INSERT INTO capture_assets(capture_id,asset_id,role) "
"SELECT image_id,asset_id,1 FROM images ORDER BY image_id;"
"INSERT INTO capture_selections(capture_id,image_id) "
"SELECT image_id,image_id FROM images ORDER BY image_id;",
"migrate legacy images to independent captures");
}
#ifdef LARDON3D_PROJECT_DB_TESTING
if (result == LARDON3D_PROJECT_DB_OK &&
getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V19")) {
result = execute(database, "INSERT INTO missing_v19_test_table VALUES(1)",
"forced migration v19 failure");
}
#endif
if (result == LARDON3D_PROJECT_DB_OK) {
result = execute(
database,
"UPDATE metadata SET value=19 WHERE key='schema_version' AND value=18",
"finish schema v19 migration");
}
}
if (result == LARDON3D_PROJECT_DB_OK) { if (result == LARDON3D_PROJECT_DB_OK) {
result = execute(database, "COMMIT", "commit migration"); result = execute(database, "COMMIT", "commit migration");
} }
@ -1163,7 +1223,12 @@ Lardon3DProjectDbResult lardon3d_project_db_open(const char *path, Lardon3DProje
"sparse_landmarks", "sparse_landmarks",
"sparse_landmark_observations", "sparse_landmark_observations",
"incremental_reconstructions", "incremental_reconstructions",
"incremental_reconstruction_tasks"}; "incremental_reconstruction_tasks",
"captures",
"capture_images",
"capture_assets",
"capture_selections",
"asset_derivations"};
for (size_t index = 0; index < sizeof(required) / sizeof(required[0]) && for (size_t index = 0; index < sizeof(required) / sizeof(required[0]) &&
result == LARDON3D_PROJECT_DB_OK; result == LARDON3D_PROJECT_DB_OK;
++index) { ++index) {
@ -2445,6 +2510,77 @@ static const char image_select[] =
"a.state,a.created_at,i.producer_task_id,i.imported_at FROM images i JOIN image_assets a ON " "a.state,a.created_at,i.producer_task_id,i.imported_at FROM images i JOIN image_assets a ON "
"a.asset_id=i.asset_id "; "a.asset_id=i.asset_id ";
Lardon3DProjectDbResult lardon3d_project_db_register_image_asset(
Lardon3DProjectDb *database,
const unsigned char sha256[LARDON3D_PROJECT_DB_SHA256_SIZE], const char *asset_path,
uint64_t size_bytes, int64_t created_at, Lardon3DProjectDbImageAsset *asset) {
if (!database || !sha256 || !valid_relative_asset_path(asset_path) ||
!canonical_asset_path(sha256, asset_path) || size_bytes > INT64_MAX || created_at < 0 ||
!asset) {
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
}
memset(asset, 0, sizeof(*asset));
(void)pthread_mutex_lock(&database->mutex);
Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin image asset register");
sqlite3_stmt *statement = NULL;
if (result == LARDON3D_PROJECT_DB_OK) {
result = prepare(database, "INSERT INTO image_assets(sha256,path,size_bytes,state,created_at) "
"VALUES(?1,?2,?3,1,?4) ON CONFLICT(sha256) DO NOTHING",
&statement);
}
if (result == LARDON3D_PROJECT_DB_OK) {
(void)sqlite3_bind_blob(statement, 1, sha256, LARDON3D_PROJECT_DB_SHA256_SIZE,
SQLITE_TRANSIENT);
(void)sqlite3_bind_text(statement, 2, asset_path, -1, SQLITE_TRANSIENT);
(void)sqlite3_bind_int64(statement, 3, (sqlite3_int64)size_bytes);
(void)sqlite3_bind_int64(statement, 4, created_at);
result = step_done(database, statement, "insert image asset");
statement = NULL;
}
if (result == LARDON3D_PROJECT_DB_OK) {
result = prepare(database, "SELECT asset_id,sha256,path,size_bytes,state,created_at FROM "
"image_assets WHERE sha256=?1", &statement);
}
if (result == LARDON3D_PROJECT_DB_OK) {
(void)sqlite3_bind_blob(statement, 1, sha256, LARDON3D_PROJECT_DB_SHA256_SIZE,
SQLITE_TRANSIENT);
int code = sqlite3_step(statement);
if (code == SQLITE_DONE) {
result = LARDON3D_PROJECT_DB_CORRUPT;
} else if (code != SQLITE_ROW) {
result = sqlite_result(database, code, "load registered image asset");
} else {
sqlite3_int64 id = sqlite3_column_int64(statement, 0);
sqlite3_int64 stored_size = sqlite3_column_int64(statement, 3);
sqlite3_int64 stored_created = sqlite3_column_int64(statement, 5);
const void *stored_hash = sqlite3_column_blob(statement, 1);
if (id <= 0 || stored_size < 0 || stored_created < 0 ||
sqlite3_column_bytes(statement, 1) != LARDON3D_PROJECT_DB_SHA256_SIZE || !stored_hash ||
!copy_column(statement, 2, asset->path, sizeof(asset->path)) ||
strcmp(asset->path, asset_path) != 0 || stored_size != (sqlite3_int64)size_bytes ||
sqlite3_column_int(statement, 4) != LARDON3D_DB_IMAGE_ASSET_READY) {
result = LARDON3D_PROJECT_DB_CORRUPT;
} else {
asset->asset_id = (uint64_t)id;
memcpy(asset->sha256, stored_hash, LARDON3D_PROJECT_DB_SHA256_SIZE);
asset->size_bytes = (uint64_t)stored_size;
asset->state = LARDON3D_DB_IMAGE_ASSET_READY;
asset->created_at = stored_created;
}
}
(void)sqlite3_finalize(statement);
statement = NULL;
}
if (result == LARDON3D_PROJECT_DB_OK) {
result = execute(database, "COMMIT", "commit image asset register");
}
if (result != LARDON3D_PROJECT_DB_OK) {
(void)execute(database, "ROLLBACK", "rollback image asset register");
}
(void)pthread_mutex_unlock(&database->mutex);
return result;
}
Lardon3DProjectDbResult lardon3d_project_db_register_image( Lardon3DProjectDbResult lardon3d_project_db_register_image(
Lardon3DProjectDb *database, uint64_t scanset_id, Lardon3DProjectDb *database, uint64_t scanset_id,
const unsigned char sha256[LARDON3D_PROJECT_DB_SHA256_SIZE], const char *asset_path, const unsigned char sha256[LARDON3D_PROJECT_DB_SHA256_SIZE], const char *asset_path,
@ -2670,6 +2806,372 @@ Lardon3DProjectDbResult lardon3d_project_db_count_images(Lardon3DProjectDb *data
return result; return result;
} }
static bool valid_capture_asset_role(Lardon3DProjectDbCaptureAssetRole role) {
return role == LARDON3D_DB_CAPTURE_ASSET_SOURCE || role == LARDON3D_DB_CAPTURE_ASSET_DERIVED;
}
static bool valid_asset_derivation_kind(Lardon3DProjectDbAssetDerivationKind kind) {
return kind == LARDON3D_DB_ASSET_DERIVATION_GENERIC_VERSIONED;
}
static bool read_capture(sqlite3_stmt *statement, Lardon3DProjectDbCapture *capture) {
sqlite3_int64 capture_id = sqlite3_column_int64(statement, 0);
sqlite3_int64 scanset_id = sqlite3_column_int64(statement, 1);
sqlite3_int64 created_at = sqlite3_column_int64(statement, 2);
if (capture_id <= 0 || scanset_id <= 0 || created_at < 0) {
return false;
}
capture->capture_id = (uint64_t)capture_id;
capture->scanset_id = (uint64_t)scanset_id;
capture->created_at = created_at;
return true;
}
Lardon3DProjectDbResult lardon3d_project_db_create_capture(
Lardon3DProjectDb *database, uint64_t scanset_id, int64_t created_at,
Lardon3DProjectDbCapture *capture) {
if (!database || !valid_catalog_id(scanset_id) || created_at < 0 || !capture) {
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
}
memset(capture, 0, sizeof(*capture));
(void)pthread_mutex_lock(&database->mutex);
sqlite3_stmt *statement = NULL;
Lardon3DProjectDbResult result = prepare(
database, "INSERT INTO captures(scanset_id,created_at) VALUES(?1,?2)", &statement);
if (result == LARDON3D_PROJECT_DB_OK) {
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)scanset_id);
(void)sqlite3_bind_int64(statement, 2, created_at);
result = step_done(database, statement, "create capture");
}
sqlite3_int64 capture_id = sqlite3_last_insert_rowid(database->connection);
if (result == LARDON3D_PROJECT_DB_OK && capture_id <= 0) {
result = LARDON3D_PROJECT_DB_CONSTRAINT;
}
if (result == LARDON3D_PROJECT_DB_OK) {
capture->capture_id = (uint64_t)capture_id;
capture->scanset_id = scanset_id;
capture->created_at = created_at;
}
(void)pthread_mutex_unlock(&database->mutex);
return result;
}
Lardon3DProjectDbResult lardon3d_project_db_load_capture(
Lardon3DProjectDb *database, uint64_t capture_id, Lardon3DProjectDbCapture *capture) {
if (!database || !valid_catalog_id(capture_id) || !capture) {
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
}
memset(capture, 0, sizeof(*capture));
(void)pthread_mutex_lock(&database->mutex);
sqlite3_stmt *statement = NULL;
Lardon3DProjectDbResult result = prepare(
database, "SELECT capture_id,scanset_id,created_at FROM captures WHERE capture_id=?1",
&statement);
if (result == LARDON3D_PROJECT_DB_OK) {
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)capture_id);
int code = sqlite3_step(statement);
if (code == SQLITE_DONE) {
result = LARDON3D_PROJECT_DB_NOT_FOUND;
} else if (code != SQLITE_ROW || !read_capture(statement, capture)) {
result = LARDON3D_PROJECT_DB_CORRUPT;
}
(void)sqlite3_finalize(statement);
}
(void)pthread_mutex_unlock(&database->mutex);
return result;
}
Lardon3DProjectDbResult lardon3d_project_db_list_captures(
Lardon3DProjectDb *database, uint64_t scanset_id, uint64_t after_capture_id,
Lardon3DProjectDbCapture *captures, size_t capacity, size_t *count) {
if (count) {
*count = 0;
}
if (!database || !valid_catalog_id(scanset_id) || after_capture_id > INT64_MAX || !captures ||
!count || capacity == 0 || capacity > LARDON3D_PROJECT_DB_CATALOG_PAGE_MAX) {
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
}
(void)pthread_mutex_lock(&database->mutex);
sqlite3_stmt *statement = NULL;
Lardon3DProjectDbResult result = prepare(
database, "SELECT capture_id,scanset_id,created_at FROM captures WHERE scanset_id=?1 "
"AND capture_id>?2 ORDER BY capture_id LIMIT ?3", &statement);
if (result == LARDON3D_PROJECT_DB_OK) {
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)scanset_id);
(void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)after_capture_id);
(void)sqlite3_bind_int64(statement, 3, (sqlite3_int64)capacity);
int code = SQLITE_DONE;
while (*count < capacity && (code = sqlite3_step(statement)) == SQLITE_ROW) {
if (!read_capture(statement, &captures[*count])) {
result = LARDON3D_PROJECT_DB_CORRUPT;
break;
}
++*count;
}
if (result == LARDON3D_PROJECT_DB_OK && *count < capacity && code != SQLITE_DONE) {
result = sqlite_result(database, code, "list captures");
}
(void)sqlite3_finalize(statement);
}
(void)pthread_mutex_unlock(&database->mutex);
return result;
}
Lardon3DProjectDbResult lardon3d_project_db_attach_capture_asset(
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t asset_id,
Lardon3DProjectDbCaptureAssetRole role) {
if (!database || !valid_catalog_id(capture_id) || !valid_catalog_id(asset_id) ||
!valid_capture_asset_role(role)) {
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
}
(void)pthread_mutex_lock(&database->mutex);
sqlite3_stmt *statement = NULL;
Lardon3DProjectDbResult result = prepare(
database, "INSERT INTO capture_assets(capture_id,asset_id,role) VALUES(?1,?2,?3)",
&statement);
if (result == LARDON3D_PROJECT_DB_OK) {
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)capture_id);
(void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)asset_id);
(void)sqlite3_bind_int(statement, 3, (int)role);
result = step_done(database, statement, "attach capture asset");
}
(void)pthread_mutex_unlock(&database->mutex);
return result;
}
Lardon3DProjectDbResult lardon3d_project_db_list_capture_assets(
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t after_asset_id,
Lardon3DProjectDbCaptureAsset *assets, size_t capacity, size_t *count) {
if (count) {
*count = 0;
}
if (!database || !valid_catalog_id(capture_id) || after_asset_id > INT64_MAX || !assets ||
!count || capacity == 0 || capacity > LARDON3D_PROJECT_DB_CATALOG_PAGE_MAX) {
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
}
(void)pthread_mutex_lock(&database->mutex);
sqlite3_stmt *statement = NULL;
Lardon3DProjectDbResult result = prepare(
database, "SELECT capture_id,asset_id,role FROM capture_assets WHERE capture_id=?1 "
"AND asset_id>?2 ORDER BY asset_id LIMIT ?3", &statement);
if (result == LARDON3D_PROJECT_DB_OK) {
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)capture_id);
(void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)after_asset_id);
(void)sqlite3_bind_int64(statement, 3, (sqlite3_int64)capacity);
int code = SQLITE_DONE;
while (*count < capacity && (code = sqlite3_step(statement)) == SQLITE_ROW) {
sqlite3_int64 stored_capture = sqlite3_column_int64(statement, 0);
sqlite3_int64 stored_asset = sqlite3_column_int64(statement, 1);
int stored_role = sqlite3_column_int(statement, 2);
if (stored_capture <= 0 || stored_asset <= 0 ||
!valid_capture_asset_role((Lardon3DProjectDbCaptureAssetRole)stored_role)) {
result = LARDON3D_PROJECT_DB_CORRUPT;
break;
}
assets[*count] = (Lardon3DProjectDbCaptureAsset){
.capture_id = (uint64_t)stored_capture,
.asset_id = (uint64_t)stored_asset,
.role = (Lardon3DProjectDbCaptureAssetRole)stored_role};
++*count;
}
if (result == LARDON3D_PROJECT_DB_OK && *count < capacity && code != SQLITE_DONE) {
result = sqlite_result(database, code, "list capture assets");
}
(void)sqlite3_finalize(statement);
}
(void)pthread_mutex_unlock(&database->mutex);
return result;
}
Lardon3DProjectDbResult lardon3d_project_db_attach_capture_image(
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t image_id) {
if (!database || !valid_catalog_id(capture_id) || !valid_catalog_id(image_id)) {
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
}
(void)pthread_mutex_lock(&database->mutex);
sqlite3_stmt *statement = NULL;
Lardon3DProjectDbResult result = prepare(
database, "INSERT INTO capture_images(capture_id,image_id) "
"SELECT ?1,?2 WHERE EXISTS(SELECT 1 FROM captures c JOIN images i "
"ON i.scanset_id=c.scanset_id WHERE c.capture_id=?1 AND i.image_id=?2)",
&statement);
if (result == LARDON3D_PROJECT_DB_OK) {
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)capture_id);
(void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)image_id);
result = step_done(database, statement, "attach capture image");
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1) {
result = LARDON3D_PROJECT_DB_NOT_FOUND;
}
}
(void)pthread_mutex_unlock(&database->mutex);
return result;
}
Lardon3DProjectDbResult lardon3d_project_db_find_capture_for_image(
Lardon3DProjectDb *database, uint64_t image_id, Lardon3DProjectDbCapture *capture) {
if (!database || !valid_catalog_id(image_id) || !capture) {
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
}
memset(capture, 0, sizeof(*capture));
(void)pthread_mutex_lock(&database->mutex);
sqlite3_stmt *statement = NULL;
Lardon3DProjectDbResult result = prepare(
database, "SELECT c.capture_id,c.scanset_id,c.created_at FROM captures c JOIN "
"capture_images ci ON ci.capture_id=c.capture_id WHERE ci.image_id=?1",
&statement);
if (result == LARDON3D_PROJECT_DB_OK) {
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)image_id);
int code = sqlite3_step(statement);
if (code == SQLITE_DONE) {
result = LARDON3D_PROJECT_DB_NOT_FOUND;
} else if (code != SQLITE_ROW || !read_capture(statement, capture)) {
result = LARDON3D_PROJECT_DB_CORRUPT;
}
(void)sqlite3_finalize(statement);
}
(void)pthread_mutex_unlock(&database->mutex);
return result;
}
Lardon3DProjectDbResult lardon3d_project_db_record_asset_derivation(
Lardon3DProjectDb *database, const Lardon3DProjectDbAssetDerivation *derivation) {
if (!database || !derivation || !valid_catalog_id(derivation->parent_asset_id) ||
!valid_catalog_id(derivation->child_asset_id) ||
derivation->parent_asset_id == derivation->child_asset_id ||
!valid_asset_derivation_kind(derivation->kind) || derivation->version == 0 ||
derivation->created_at < 0 ||
(derivation->has_producer_task && !valid_task_id(derivation->producer_task_id))) {
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
}
(void)pthread_mutex_lock(&database->mutex);
sqlite3_stmt *statement = NULL;
Lardon3DProjectDbResult result = prepare(
database, "INSERT INTO asset_derivations(child_asset_id,parent_asset_id,derivation_kind,"
"derivation_version,parameter_fingerprint,producer_task_id,created_at) "
"VALUES(?1,?2,?3,?4,?5,?6,?7)", &statement);
if (result == LARDON3D_PROJECT_DB_OK) {
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)derivation->child_asset_id);
(void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)derivation->parent_asset_id);
(void)sqlite3_bind_int(statement, 3, (int)derivation->kind);
(void)sqlite3_bind_int64(statement, 4, (sqlite3_int64)derivation->version);
(void)sqlite3_bind_blob(statement, 5, derivation->parameter_fingerprint,
LARDON3D_PROJECT_DB_SHA256_SIZE, SQLITE_TRANSIENT);
if (derivation->has_producer_task) {
(void)sqlite3_bind_int64(statement, 6, (sqlite3_int64)derivation->producer_task_id);
} else {
(void)sqlite3_bind_null(statement, 6);
}
(void)sqlite3_bind_int64(statement, 7, derivation->created_at);
result = step_done(database, statement, "record asset derivation");
}
(void)pthread_mutex_unlock(&database->mutex);
return result;
}
Lardon3DProjectDbResult lardon3d_project_db_load_asset_derivation(
Lardon3DProjectDb *database, uint64_t child_asset_id,
Lardon3DProjectDbAssetDerivation *derivation) {
if (!database || !valid_catalog_id(child_asset_id) || !derivation) {
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
}
memset(derivation, 0, sizeof(*derivation));
(void)pthread_mutex_lock(&database->mutex);
sqlite3_stmt *statement = NULL;
Lardon3DProjectDbResult result = prepare(
database, "SELECT parent_asset_id,child_asset_id,derivation_kind,derivation_version,"
"parameter_fingerprint,producer_task_id,created_at FROM asset_derivations "
"WHERE child_asset_id=?1", &statement);
if (result == LARDON3D_PROJECT_DB_OK) {
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)child_asset_id);
int code = sqlite3_step(statement);
if (code == SQLITE_DONE) {
result = LARDON3D_PROJECT_DB_NOT_FOUND;
} else if (code != SQLITE_ROW) {
result = sqlite_result(database, code, "load asset derivation");
} else {
sqlite3_int64 parent = sqlite3_column_int64(statement, 0);
sqlite3_int64 child = sqlite3_column_int64(statement, 1);
sqlite3_int64 version = sqlite3_column_int64(statement, 3);
sqlite3_int64 producer = sqlite3_column_int64(statement, 5);
sqlite3_int64 created_at = sqlite3_column_int64(statement, 6);
const void *fingerprint = sqlite3_column_blob(statement, 4);
if (parent <= 0 || child <= 0 || parent == child || version <= 0 || version > UINT32_MAX ||
created_at < 0 ||
!valid_asset_derivation_kind((Lardon3DProjectDbAssetDerivationKind)
sqlite3_column_int(statement, 2)) ||
sqlite3_column_bytes(statement, 4) != LARDON3D_PROJECT_DB_SHA256_SIZE || !fingerprint ||
(sqlite3_column_type(statement, 5) != SQLITE_NULL && producer <= 0)) {
result = LARDON3D_PROJECT_DB_CORRUPT;
} else {
derivation->parent_asset_id = (uint64_t)parent;
derivation->child_asset_id = (uint64_t)child;
derivation->kind = (Lardon3DProjectDbAssetDerivationKind)sqlite3_column_int(statement, 2);
derivation->version = (uint32_t)version;
memcpy(derivation->parameter_fingerprint, fingerprint,
LARDON3D_PROJECT_DB_SHA256_SIZE);
derivation->has_producer_task = sqlite3_column_type(statement, 5) != SQLITE_NULL;
derivation->producer_task_id = derivation->has_producer_task ? (uint64_t)producer : 0;
derivation->created_at = created_at;
}
}
(void)sqlite3_finalize(statement);
}
(void)pthread_mutex_unlock(&database->mutex);
return result;
}
Lardon3DProjectDbResult lardon3d_project_db_set_selected_capture_image(
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t image_id) {
if (!database || !valid_catalog_id(capture_id) || !valid_catalog_id(image_id)) {
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
}
(void)pthread_mutex_lock(&database->mutex);
sqlite3_stmt *statement = NULL;
Lardon3DProjectDbResult result = prepare(
database, "INSERT INTO capture_selections(capture_id,image_id) VALUES(?1,?2) "
"ON CONFLICT(capture_id) DO UPDATE SET image_id=excluded.image_id", &statement);
if (result == LARDON3D_PROJECT_DB_OK) {
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)capture_id);
(void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)image_id);
result = step_done(database, statement, "set selected capture image");
}
(void)pthread_mutex_unlock(&database->mutex);
return result;
}
Lardon3DProjectDbResult lardon3d_project_db_get_selected_capture_image(
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t *image_id) {
if (image_id) {
*image_id = 0;
}
if (!database || !valid_catalog_id(capture_id) || !image_id) {
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
}
(void)pthread_mutex_lock(&database->mutex);
sqlite3_stmt *statement = NULL;
Lardon3DProjectDbResult result = prepare(
database, "SELECT image_id FROM capture_selections WHERE capture_id=?1", &statement);
if (result == LARDON3D_PROJECT_DB_OK) {
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)capture_id);
int code = sqlite3_step(statement);
if (code == SQLITE_DONE) {
result = LARDON3D_PROJECT_DB_NOT_FOUND;
} else if (code != SQLITE_ROW) {
result = sqlite_result(database, code, "get selected capture image");
} else {
sqlite3_int64 selected = sqlite3_column_int64(statement, 0);
if (selected <= 0) {
result = LARDON3D_PROJECT_DB_CORRUPT;
} else {
*image_id = (uint64_t)selected;
}
}
(void)sqlite3_finalize(statement);
}
(void)pthread_mutex_unlock(&database->mutex);
return result;
}
Lardon3DProjectDbResult Lardon3DProjectDbResult
lardon3d_project_db_create_artifact(Lardon3DProjectDb *database, lardon3d_project_db_create_artifact(Lardon3DProjectDb *database,
const Lardon3DProjectDbArtifact *artifact) { const Lardon3DProjectDbArtifact *artifact) {

View file

@ -172,6 +172,11 @@ static bool create_v11_database(const char *path) {
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
return execute_sql(path, return execute_sql(path,
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;" "PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
"DROP TABLE IF EXISTS asset_derivations;"
"DROP TABLE IF EXISTS capture_selections;"
"DROP TABLE IF EXISTS capture_assets;"
"DROP TABLE IF EXISTS capture_images;"
"DROP TABLE IF EXISTS captures;"
"DROP TABLE IF EXISTS incremental_reconstruction_tasks;" "DROP TABLE IF EXISTS incremental_reconstruction_tasks;"
"DROP TABLE IF EXISTS incremental_reconstructions;" "DROP TABLE IF EXISTS incremental_reconstructions;"
"DROP TABLE IF EXISTS sparse_sfm_tasks;" "DROP TABLE IF EXISTS sparse_sfm_tasks;"
@ -197,7 +202,7 @@ static bool test_model_api(const char *path) {
Lardon3DProjectDb *database = NULL; Lardon3DProjectDb *database = NULL;
CHECK(lardon3d_project_db_open(path, &database, error) == CHECK(lardon3d_project_db_open(path, &database, error) ==
LARDON3D_PROJECT_DB_OK); LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 18); CHECK(lardon3d_project_db_schema_version(database) == 19);
Parents parents; Parents parents;
CHECK(create_parents(database, &parents)); CHECK(create_parents(database, &parents));
@ -481,7 +486,7 @@ static bool test_migration(const char *v11_path, const char *failed_path) {
0)); 0));
CHECK(lardon3d_project_db_open(v11_path, &database, error) == CHECK(lardon3d_project_db_open(v11_path, &database, error) ==
LARDON3D_PROJECT_DB_OK); LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 18); CHECK(lardon3d_project_db_schema_version(database) == 19);
Parents parents; Parents parents;
CHECK(create_parents(database, &parents)); CHECK(create_parents(database, &parents));
unsigned char fingerprint[32] = {0x91}; unsigned char fingerprint[32] = {0x91};
@ -496,7 +501,7 @@ static bool test_migration(const char *v11_path, const char *failed_path) {
migrated_result.geometric_verification_result_id; migrated_result.geometric_verification_result_id;
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
CHECK(query_integer( CHECK(query_integer(
v11_path, "SELECT value FROM metadata WHERE key='schema_version'", 18)); v11_path, "SELECT value FROM metadata WHERE key='schema_version'", 19));
CHECK( CHECK(
query_integer(v11_path, query_integer(v11_path,
"SELECT count(*) FROM sqlite_master WHERE type='index' AND " "SELECT count(*) FROM sqlite_master WHERE type='index' AND "
@ -525,7 +530,7 @@ static bool test_migration(const char *v11_path, const char *failed_path) {
0)); 0));
CHECK(lardon3d_project_db_open(failed_path, &database, error) == CHECK(lardon3d_project_db_open(failed_path, &database, error) ==
LARDON3D_PROJECT_DB_OK); LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 18); CHECK(lardon3d_project_db_schema_version(database) == 19);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
return true; return true;
} }

View file

@ -332,6 +332,11 @@ static bool run_task_test(void) {
CHECK(exec_sql( CHECK(exec_sql(
database_path, database_path,
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;" "PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
"DROP TABLE IF EXISTS asset_derivations;"
"DROP TABLE IF EXISTS capture_selections;"
"DROP TABLE IF EXISTS capture_assets;"
"DROP TABLE IF EXISTS capture_images;"
"DROP TABLE IF EXISTS captures;"
"DROP TABLE IF EXISTS incremental_reconstruction_tasks;" "DROP TABLE IF EXISTS incremental_reconstruction_tasks;"
"DROP TABLE IF EXISTS incremental_reconstructions;" "DROP TABLE IF EXISTS incremental_reconstructions;"
"DROP TABLE IF EXISTS sparse_sfm_tasks;" "DROP TABLE IF EXISTS sparse_sfm_tasks;"
@ -364,7 +369,7 @@ static bool run_task_test(void) {
0)); 0));
CHECK(lardon3d_project_db_open(database_path, &database, error) == CHECK(lardon3d_project_db_open(database_path, &database, error) ==
LARDON3D_PROJECT_DB_OK); LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 18); CHECK(lardon3d_project_db_schema_version(database) == 19);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
CHECK(remove_tree(root)); CHECK(remove_tree(root));
return true; return true;

View file

@ -53,6 +53,11 @@ static bool create_v9_database(const char *path) {
return false; return false;
static const char sql[] = static const char sql[] =
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;" "PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
"DROP TABLE IF EXISTS asset_derivations;"
"DROP TABLE IF EXISTS capture_selections;"
"DROP TABLE IF EXISTS capture_assets;"
"DROP TABLE IF EXISTS capture_images;"
"DROP TABLE IF EXISTS captures;"
"DROP TABLE IF EXISTS incremental_reconstruction_tasks;" "DROP TABLE IF EXISTS incremental_reconstruction_tasks;"
"DROP TABLE IF EXISTS incremental_reconstructions;" "DROP TABLE IF EXISTS incremental_reconstructions;"
"DROP TABLE IF EXISTS sparse_sfm_tasks;" "DROP TABLE IF EXISTS sparse_sfm_tasks;"
@ -119,7 +124,7 @@ static bool run_test(void) {
Lardon3DProjectDb *database = NULL; Lardon3DProjectDb *database = NULL;
CHECK(lardon3d_project_db_open(database_path, &database, error) == CHECK(lardon3d_project_db_open(database_path, &database, error) ==
LARDON3D_PROJECT_DB_OK); LARDON3D_PROJECT_DB_OK);
CHECK(database && lardon3d_project_db_schema_version(database) == 18); CHECK(database && lardon3d_project_db_schema_version(database) == 19);
Lardon3DProjectDbScanSet scanset; Lardon3DProjectDbScanSet scanset;
CHECK(lardon3d_project_db_create_scanset(database, "Match-test", &scanset) == CHECK(lardon3d_project_db_create_scanset(database, "Match-test", &scanset) ==
@ -496,7 +501,7 @@ static bool run_test(void) {
CHECK(lardon3d_project_db_open(database_path, &database, error) == CHECK(lardon3d_project_db_open(database_path, &database, error) ==
LARDON3D_PROJECT_DB_OK); LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 18); CHECK(lardon3d_project_db_schema_version(database) == 19);
/* Verify persistence: load previously created results */ /* Verify persistence: load previously created results */
CHECK(lardon3d_project_db_load_match_result(database, first_id, &loaded) == CHECK(lardon3d_project_db_load_match_result(database, first_id, &loaded) ==
@ -539,11 +544,11 @@ static bool run_test(void) {
v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 9)); v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 9));
CHECK(lardon3d_project_db_open(v9_path, &database, error) == CHECK(lardon3d_project_db_open(v9_path, &database, error) ==
LARDON3D_PROJECT_DB_OK); LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 18); CHECK(lardon3d_project_db_schema_version(database) == 19);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
database = NULL; database = NULL;
CHECK(query_integer( CHECK(query_integer(
v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 18)); v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 19));
CHECK( CHECK(
query_integer(v9_path, query_integer(v9_path,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND " "SELECT count(*) FROM sqlite_master WHERE type='table' AND "
@ -571,7 +576,7 @@ static bool run_test(void) {
0)); 0));
CHECK(lardon3d_project_db_open(failed_v10_path, &database, error) == CHECK(lardon3d_project_db_open(failed_v10_path, &database, error) ==
LARDON3D_PROJECT_DB_OK && LARDON3D_PROJECT_DB_OK &&
lardon3d_project_db_schema_version(database) == 18); lardon3d_project_db_schema_version(database) == 19);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
database = NULL; database = NULL;

View file

@ -89,6 +89,11 @@ static bool downgrade_project_to_historical_v10(const char *database_path) {
} }
static const char sql[] = static const char sql[] =
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;" "PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
"DROP TABLE IF EXISTS asset_derivations;"
"DROP TABLE IF EXISTS capture_selections;"
"DROP TABLE IF EXISTS capture_assets;"
"DROP TABLE IF EXISTS capture_images;"
"DROP TABLE IF EXISTS captures;"
"DROP TABLE IF EXISTS incremental_reconstruction_tasks;" "DROP TABLE IF EXISTS incremental_reconstruction_tasks;"
"DROP TABLE IF EXISTS incremental_reconstructions;" "DROP TABLE IF EXISTS incremental_reconstructions;"
"DROP TABLE IF EXISTS sparse_sfm_tasks;" "DROP TABLE IF EXISTS sparse_sfm_tasks;"
@ -368,7 +373,7 @@ static bool run_test(void) {
"name='matcher_tasks'", "name='matcher_tasks'",
0)); 0));
CHECK(reopen_runtime(&fixture)); CHECK(reopen_runtime(&fixture));
CHECK(lardon3d_project_db_schema_version(fixture.state.project_db) == 18); CHECK(lardon3d_project_db_schema_version(fixture.state.project_db) == 19);
CHECK( CHECK(
query_integer(database_path, query_integer(database_path,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND " "SELECT count(*) FROM sqlite_master WHERE type='table' AND "

View file

@ -116,7 +116,7 @@ static bool create_future_database(const char *path) {
sqlite3_exec( sqlite3_exec(
connection, connection,
"CREATE TABLE metadata(key TEXT PRIMARY KEY,value INTEGER NOT NULL);" "CREATE TABLE metadata(key TEXT PRIMARY KEY,value INTEGER NOT NULL);"
"INSERT INTO metadata VALUES('schema_version',19);", "INSERT INTO metadata VALUES('schema_version',20);",
NULL, NULL, NULL) == SQLITE_OK; NULL, NULL, NULL) == SQLITE_OK;
return sqlite3_close(connection) == SQLITE_OK && ok; return sqlite3_close(connection) == SQLITE_OK && ok;
} }
@ -133,6 +133,8 @@ static bool create_v7_database(const char *path) {
return false; return false;
static const char sql[] = static const char sql[] =
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;" "PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
"DROP TABLE asset_derivations;DROP TABLE capture_selections;DROP TABLE capture_assets;"
"DROP TABLE capture_images;DROP TABLE captures;"
"DROP TABLE incremental_reconstruction_tasks;" "DROP TABLE incremental_reconstruction_tasks;"
"DROP TABLE incremental_reconstructions;" "DROP TABLE incremental_reconstructions;"
"DROP TABLE sparse_sfm_tasks;" "DROP TABLE sparse_sfm_tasks;"
@ -172,6 +174,8 @@ static bool create_v6_database(const char *path) {
return false; return false;
static const char sql[] = static const char sql[] =
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;" "PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
"DROP TABLE asset_derivations;DROP TABLE capture_selections;DROP TABLE capture_assets;"
"DROP TABLE capture_images;DROP TABLE captures;"
"DROP TABLE incremental_reconstruction_tasks;" "DROP TABLE incremental_reconstruction_tasks;"
"DROP TABLE incremental_reconstructions;" "DROP TABLE incremental_reconstructions;"
"DROP TABLE sparse_sfm_tasks;" "DROP TABLE sparse_sfm_tasks;"
@ -219,6 +223,8 @@ static bool create_v10_database(const char *path) {
} }
static const char sql[] = static const char sql[] =
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;" "PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
"DROP TABLE asset_derivations;DROP TABLE capture_selections;DROP TABLE capture_assets;"
"DROP TABLE capture_images;DROP TABLE captures;"
"DROP TABLE incremental_reconstruction_tasks;" "DROP TABLE incremental_reconstruction_tasks;"
"DROP TABLE incremental_reconstructions;" "DROP TABLE incremental_reconstructions;"
"DROP TABLE sparse_sfm_tasks;" "DROP TABLE sparse_sfm_tasks;"
@ -255,6 +261,8 @@ static bool create_v13_database(const char *path) {
return false; return false;
static const char sql[] = static const char sql[] =
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;" "PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
"DROP TABLE asset_derivations;DROP TABLE capture_selections;DROP TABLE capture_assets;"
"DROP TABLE capture_images;DROP TABLE captures;"
"DROP TABLE incremental_reconstruction_tasks;" "DROP TABLE incremental_reconstruction_tasks;"
"DROP TABLE incremental_reconstructions;" "DROP TABLE incremental_reconstructions;"
"DROP TABLE sparse_sfm_tasks;" "DROP TABLE sparse_sfm_tasks;"
@ -879,7 +887,7 @@ static bool run_test(void) {
char v10_path[512], failed_v11_path[512]; char v10_path[512], failed_v11_path[512];
char v13_path[512], true_v14_path[512], failed_v14_path[512], char v13_path[512], true_v14_path[512], failed_v14_path[512],
failed_v15_path[512], true_v15_path[512], v17_path[512], failed_v15_path[512], true_v15_path[512], v17_path[512],
failed_v18_path[512]; failed_v18_path[512], failed_v19_path[512];
CHECK(snprintf(database_path, sizeof(database_path), "%s/project.db", CHECK(snprintf(database_path, sizeof(database_path), "%s/project.db",
directory) > 0); directory) > 0);
CHECK(snprintf(artifact_path, sizeof(artifact_path), "%s/artifact.bin", CHECK(snprintf(artifact_path, sizeof(artifact_path), "%s/artifact.bin",
@ -925,12 +933,14 @@ static bool run_test(void) {
CHECK(snprintf(v17_path, sizeof(v17_path), "%s/v17.db", directory) > 0); CHECK(snprintf(v17_path, sizeof(v17_path), "%s/v17.db", directory) > 0);
CHECK(snprintf(failed_v18_path, sizeof(failed_v18_path), CHECK(snprintf(failed_v18_path, sizeof(failed_v18_path),
"%s/failed-v18.db", directory) > 0); "%s/failed-v18.db", directory) > 0);
CHECK(snprintf(failed_v19_path, sizeof(failed_v19_path),
"%s/failed-v19.db", directory) > 0);
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]; char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
Lardon3DProjectDb *database = NULL; Lardon3DProjectDb *database = NULL;
CHECK(lardon3d_project_db_open(database_path, &database, error) == CHECK(lardon3d_project_db_open(database_path, &database, error) ==
LARDON3D_PROJECT_DB_OK); LARDON3D_PROJECT_DB_OK);
CHECK(database && lardon3d_project_db_schema_version(database) == 18); CHECK(database && lardon3d_project_db_schema_version(database) == 19);
bool legacy_pending = true; bool legacy_pending = true;
CHECK(lardon3d_project_db_legacy_catalog_pending(database, &legacy_pending) == CHECK(lardon3d_project_db_legacy_catalog_pending(database, &legacy_pending) ==
LARDON3D_PROJECT_DB_OK && LARDON3D_PROJECT_DB_OK &&
@ -1054,6 +1064,106 @@ static bool run_test(void) {
LARDON3D_PROJECT_DB_CANDIDATE_PAIR_PAGE_MAX + 1, LARDON3D_PROJECT_DB_CANDIDATE_PAIR_PAGE_MAX + 1,
&pair_count) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); &pair_count) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* S1: Capture is a catalog policy layer; image_id and asset_id stay immutable. */
Lardon3DProjectDbCapture capture, other_capture, mapped_capture;
CHECK(lardon3d_project_db_create_capture(database, replacement_scanset.scanset_id, 20,
&capture) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_create_capture(database, replacement_scanset.scanset_id, 21,
&other_capture) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_create_capture(database, UINT64_C(999999), 22, &mapped_capture) ==
LARDON3D_PROJECT_DB_CONSTRAINT);
Lardon3DProjectDbCapture capture_page[2];
size_t capture_count = 0;
CHECK(lardon3d_project_db_list_captures(database, replacement_scanset.scanset_id, 0,
capture_page, 1, &capture_count) ==
LARDON3D_PROJECT_DB_OK &&
capture_count == 1 && capture_page[0].capture_id == capture.capture_id);
uint64_t selected_image = 0;
CHECK(lardon3d_project_db_get_selected_capture_image(database, capture.capture_id,
&selected_image) ==
LARDON3D_PROJECT_DB_NOT_FOUND);
unsigned char raw_hash[LARDON3D_PROJECT_DB_SHA256_SIZE] = {5};
char raw_asset_path[LARDON3D_PROJECT_DB_PATH_CAPACITY];
asset_path_for_hash(raw_hash, raw_asset_path);
Lardon3DProjectDbImageAsset raw_asset;
CHECK(lardon3d_project_db_register_image_asset(database, raw_hash, raw_asset_path, 2, 22,
&raw_asset) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_attach_capture_asset(
database, capture.capture_id, raw_asset.asset_id,
LARDON3D_DB_CAPTURE_ASSET_SOURCE) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_attach_capture_asset(
database, capture.capture_id, pair_image.asset_id,
LARDON3D_DB_CAPTURE_ASSET_SOURCE) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_attach_capture_asset(
database, capture.capture_id, raw_asset.asset_id,
(Lardon3DProjectDbCaptureAssetRole)99) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
CHECK(lardon3d_project_db_attach_capture_asset(
database, capture.capture_id, raw_asset.asset_id,
LARDON3D_DB_CAPTURE_ASSET_SOURCE) == LARDON3D_PROJECT_DB_CONSTRAINT);
unsigned char developed_hash[LARDON3D_PROJECT_DB_SHA256_SIZE] = {6};
char developed_asset_path[LARDON3D_PROJECT_DB_PATH_CAPACITY];
asset_path_for_hash(developed_hash, developed_asset_path);
Lardon3DProjectDbImage developed_image;
CHECK(lardon3d_project_db_register_image(
database, replacement_scanset.scanset_id, developed_hash, developed_asset_path, 3,
"developed.png", "/source/developed.png", 0, 23, &identity_status,
&developed_image) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_attach_capture_asset(
database, capture.capture_id, developed_image.asset_id,
LARDON3D_DB_CAPTURE_ASSET_DERIVED) == LARDON3D_PROJECT_DB_OK);
Lardon3DProjectDbCaptureAsset capture_assets[3];
size_t capture_asset_count = 0;
CHECK(lardon3d_project_db_list_capture_assets(database, capture.capture_id, 0, capture_assets,
3, &capture_asset_count) ==
LARDON3D_PROJECT_DB_OK &&
capture_asset_count == 3);
CHECK(lardon3d_project_db_attach_capture_image(database, capture.capture_id,
pair_image.image_id) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_attach_capture_image(database, other_capture.capture_id,
pair_image.image_id) ==
LARDON3D_PROJECT_DB_CONSTRAINT);
CHECK(lardon3d_project_db_attach_capture_image(database, capture.capture_id,
developed_image.image_id) ==
LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_find_capture_for_image(database, pair_image.image_id,
&mapped_capture) == LARDON3D_PROJECT_DB_OK &&
mapped_capture.capture_id == capture.capture_id);
CHECK(lardon3d_project_db_set_selected_capture_image(database, capture.capture_id,
pair_image.image_id) ==
LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_set_selected_capture_image(database, other_capture.capture_id,
developed_image.image_id) ==
LARDON3D_PROJECT_DB_CONSTRAINT);
CHECK(lardon3d_project_db_set_selected_capture_image(database, capture.capture_id,
developed_image.image_id) ==
LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_get_selected_capture_image(database, capture.capture_id,
&selected_image) ==
LARDON3D_PROJECT_DB_OK &&
selected_image == developed_image.image_id && pair_image.image_id != developed_image.image_id);
Lardon3DProjectDbAssetDerivation derivation = {
.parent_asset_id = raw_asset.asset_id,
.child_asset_id = developed_image.asset_id,
.kind = LARDON3D_DB_ASSET_DERIVATION_GENERIC_VERSIONED,
.version = 1,
.created_at = 24};
memset(derivation.parameter_fingerprint, 0xA5, sizeof(derivation.parameter_fingerprint));
CHECK(lardon3d_project_db_record_asset_derivation(database, &derivation) ==
LARDON3D_PROJECT_DB_OK);
Lardon3DProjectDbAssetDerivation loaded_derivation;
CHECK(lardon3d_project_db_load_asset_derivation(database, developed_image.asset_id,
&loaded_derivation) == LARDON3D_PROJECT_DB_OK &&
loaded_derivation.parent_asset_id == raw_asset.asset_id &&
loaded_derivation.version == 1 &&
memcmp(loaded_derivation.parameter_fingerprint, derivation.parameter_fingerprint,
sizeof(derivation.parameter_fingerprint)) == 0);
derivation.parent_asset_id = pair_image.asset_id;
CHECK(lardon3d_project_db_record_asset_derivation(database, &derivation) ==
LARDON3D_PROJECT_DB_CONSTRAINT);
derivation.parent_asset_id = developed_image.asset_id;
CHECK(lardon3d_project_db_record_asset_derivation(database, &derivation) ==
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
Lardon3DProjectDbProject project = {.created_at = 100, .updated_at = 100}; Lardon3DProjectDbProject project = {.created_at = 100, .updated_at = 100};
(void)snprintf(project.stable_id, sizeof(project.stable_id), "project-0001"); (void)snprintf(project.stable_id, sizeof(project.stable_id), "project-0001");
(void)snprintf(project.name, sizeof(project.name), "Projet test"); (void)snprintf(project.name, sizeof(project.name), "Projet test");
@ -1248,7 +1358,7 @@ static bool run_test(void) {
database = NULL; database = NULL;
CHECK(query_integer(database_path, CHECK(query_integer(database_path,
"SELECT value FROM metadata WHERE key='schema_version'", "SELECT value FROM metadata WHERE key='schema_version'",
18)); 19));
CHECK(query_integer(database_path, CHECK(query_integer(database_path,
"SELECT count(*) FROM tasks WHERE task_id=1", 1)); "SELECT count(*) FROM tasks WHERE task_id=1", 1));
@ -1270,10 +1380,12 @@ static bool run_test(void) {
"created_at) VALUES(1,1,1,1,1,zeroblob(32),1,2,1,0,0,1);" "created_at) VALUES(1,1,1,1,1,zeroblob(32),1,2,1,0,0,1);"
"DROP TABLE incremental_reconstruction_tasks;" "DROP TABLE incremental_reconstruction_tasks;"
"DROP TABLE incremental_reconstructions;" "DROP TABLE incremental_reconstructions;"
"DROP TABLE asset_derivations;DROP TABLE capture_selections;DROP TABLE capture_assets;"
"DROP TABLE capture_images;DROP TABLE captures;"
"UPDATE metadata SET value=17 WHERE key='schema_version';")); "UPDATE metadata SET value=17 WHERE key='schema_version';"));
CHECK(lardon3d_project_db_open(v17_path, &database, error) == CHECK(lardon3d_project_db_open(v17_path, &database, error) ==
LARDON3D_PROJECT_DB_OK); LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 18); CHECK(lardon3d_project_db_schema_version(database) == 19);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
database = NULL; database = NULL;
CHECK(query_integer(v17_path, CHECK(query_integer(v17_path,
@ -1290,10 +1402,36 @@ static bool run_test(void) {
LARDON3D_PROJECT_DB_OK); LARDON3D_PROJECT_DB_OK);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
database = NULL; database = NULL;
CHECK(lardon3d_project_db_open(failed_v19_path, &database, error) ==
LARDON3D_PROJECT_DB_OK);
lardon3d_project_db_close(database);
database = NULL;
CHECK(execute_test_sql(
failed_v19_path,
"DROP TABLE asset_derivations;DROP TABLE capture_selections;DROP TABLE capture_assets;"
"DROP TABLE capture_images;DROP TABLE captures;"
"UPDATE metadata SET value=18 WHERE key='schema_version';"));
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V19", "1", 1) == 0);
CHECK(lardon3d_project_db_open(failed_v19_path, &database, error) ==
LARDON3D_PROJECT_DB_IO_ERROR);
CHECK(unsetenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V19") == 0);
CHECK(query_integer(failed_v19_path,
"SELECT value FROM metadata WHERE key='schema_version'", 18));
CHECK(query_integer(failed_v19_path,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
"name='captures'", 0));
CHECK(lardon3d_project_db_open(failed_v19_path, &database, error) ==
LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 19);
lardon3d_project_db_close(database);
database = NULL;
CHECK(execute_test_sql( CHECK(execute_test_sql(
failed_v18_path, failed_v18_path,
"DROP TABLE incremental_reconstruction_tasks;" "DROP TABLE incremental_reconstruction_tasks;"
"DROP TABLE incremental_reconstructions;" "DROP TABLE incremental_reconstructions;"
"DROP TABLE asset_derivations;DROP TABLE capture_selections;DROP TABLE capture_assets;"
"DROP TABLE capture_images;DROP TABLE captures;"
"UPDATE metadata SET value=17 WHERE key='schema_version';")); "UPDATE metadata SET value=17 WHERE key='schema_version';"));
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V18", "1", 1) == 0); CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V18", "1", 1) == 0);
CHECK(lardon3d_project_db_open(failed_v18_path, &database, error) == CHECK(lardon3d_project_db_open(failed_v18_path, &database, error) ==
@ -1336,7 +1474,7 @@ static bool run_test(void) {
CHECK(create_v1_database(legacy_path)); CHECK(create_v1_database(legacy_path));
CHECK(lardon3d_project_db_open(legacy_path, &database, error) == CHECK(lardon3d_project_db_open(legacy_path, &database, error) ==
LARDON3D_PROJECT_DB_OK); LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 18); CHECK(lardon3d_project_db_schema_version(database) == 19);
CHECK(lardon3d_project_db_get_project(database, &loaded_project) == CHECK(lardon3d_project_db_get_project(database, &loaded_project) ==
LARDON3D_PROJECT_DB_OK && LARDON3D_PROJECT_DB_OK &&
strcmp(loaded_project.stable_id, "legacy-project") == 0); strcmp(loaded_project.stable_id, "legacy-project") == 0);
@ -1351,7 +1489,7 @@ static bool run_test(void) {
database = NULL; database = NULL;
CHECK(query_integer(legacy_path, CHECK(query_integer(legacy_path,
"SELECT value FROM metadata WHERE key='schema_version'", "SELECT value FROM metadata WHERE key='schema_version'",
18)); 19));
CHECK(create_v1_database(failed_migration_path)); CHECK(create_v1_database(failed_migration_path));
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V2", "1", 1) == 0); CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V2", "1", 1) == 0);
@ -1382,7 +1520,7 @@ static bool run_test(void) {
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
database = NULL; database = NULL;
CHECK(query_integer( CHECK(query_integer(
v2_path, "SELECT value FROM metadata WHERE key='schema_version'", 18)); v2_path, "SELECT value FROM metadata WHERE key='schema_version'", 19));
CHECK(create_v2_database(failed_v3_migration_path)); CHECK(create_v2_database(failed_v3_migration_path));
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V3", "1", 1) == 0); CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V3", "1", 1) == 0);
@ -1416,7 +1554,7 @@ static bool run_test(void) {
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
database = NULL; database = NULL;
CHECK(query_integer( CHECK(query_integer(
v3_path, "SELECT value FROM metadata WHERE key='schema_version'", 18)); v3_path, "SELECT value FROM metadata WHERE key='schema_version'", 19));
CHECK(create_v3_database(failed_v4_path)); CHECK(create_v3_database(failed_v4_path));
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V4", "1", 1) == 0); CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V4", "1", 1) == 0);
@ -1438,7 +1576,7 @@ static bool run_test(void) {
fprintf(stderr, "Migration v4 (%d): %s\n", (int)v4_result, error); fprintf(stderr, "Migration v4 (%d): %s\n", (int)v4_result, error);
} }
CHECK(v4_result == LARDON3D_PROJECT_DB_OK); CHECK(v4_result == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 18); CHECK(lardon3d_project_db_schema_version(database) == 19);
CHECK(lardon3d_project_db_load_task(database, 9, &task) == CHECK(lardon3d_project_db_load_task(database, 9, &task) ==
LARDON3D_PROJECT_DB_OK); LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_load_artifact(database, "legacy-artifact", CHECK(lardon3d_project_db_load_artifact(database, "legacy-artifact",
@ -1493,7 +1631,7 @@ static bool run_test(void) {
error); error);
} }
CHECK(retry_v7 == LARDON3D_PROJECT_DB_OK && CHECK(retry_v7 == LARDON3D_PROJECT_DB_OK &&
lardon3d_project_db_schema_version(database) == 18); lardon3d_project_db_schema_version(database) == 19);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
database = NULL; database = NULL;
@ -1503,7 +1641,7 @@ static bool run_test(void) {
5)); 5));
CHECK(lardon3d_project_db_open(direct_v5_path, &database, error) == CHECK(lardon3d_project_db_open(direct_v5_path, &database, error) ==
LARDON3D_PROJECT_DB_OK && LARDON3D_PROJECT_DB_OK &&
lardon3d_project_db_schema_version(database) == 18); lardon3d_project_db_schema_version(database) == 19);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
database = NULL; database = NULL;
@ -1512,11 +1650,11 @@ static bool run_test(void) {
v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 7)); v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 7));
CHECK(lardon3d_project_db_open(v8_path, &database, error) == CHECK(lardon3d_project_db_open(v8_path, &database, error) ==
LARDON3D_PROJECT_DB_OK); LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 18); CHECK(lardon3d_project_db_schema_version(database) == 19);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
database = NULL; database = NULL;
CHECK(query_integer( CHECK(query_integer(
v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 18)); v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 19));
CHECK(create_v7_database(failed_v8_path)); CHECK(create_v7_database(failed_v8_path));
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V8", "1", 1) == 0); CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V8", "1", 1) == 0);
@ -1533,7 +1671,7 @@ static bool run_test(void) {
0)); 0));
CHECK(lardon3d_project_db_open(failed_v8_path, &database, error) == CHECK(lardon3d_project_db_open(failed_v8_path, &database, error) ==
LARDON3D_PROJECT_DB_OK && LARDON3D_PROJECT_DB_OK &&
lardon3d_project_db_schema_version(database) == 18); lardon3d_project_db_schema_version(database) == 19);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
database = NULL; database = NULL;
@ -1552,11 +1690,11 @@ static bool run_test(void) {
0)); 0));
CHECK(lardon3d_project_db_open(v10_path, &database, error) == CHECK(lardon3d_project_db_open(v10_path, &database, error) ==
LARDON3D_PROJECT_DB_OK && LARDON3D_PROJECT_DB_OK &&
lardon3d_project_db_schema_version(database) == 18); lardon3d_project_db_schema_version(database) == 19);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
database = NULL; database = NULL;
CHECK(query_integer( CHECK(query_integer(
v10_path, "SELECT value FROM metadata WHERE key='schema_version'", 18)); v10_path, "SELECT value FROM metadata WHERE key='schema_version'", 19));
CHECK( CHECK(
query_integer(v10_path, query_integer(v10_path,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND " "SELECT count(*) FROM sqlite_master WHERE type='table' AND "
@ -1583,7 +1721,7 @@ static bool run_test(void) {
1)); 1));
CHECK(lardon3d_project_db_open(failed_v11_path, &database, error) == CHECK(lardon3d_project_db_open(failed_v11_path, &database, error) ==
LARDON3D_PROJECT_DB_OK); LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 18); CHECK(lardon3d_project_db_schema_version(database) == 19);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
database = NULL; database = NULL;
@ -1597,11 +1735,11 @@ static bool run_test(void) {
0)); 0));
CHECK(lardon3d_project_db_open(v13_path, &database, error) == CHECK(lardon3d_project_db_open(v13_path, &database, error) ==
LARDON3D_PROJECT_DB_OK && LARDON3D_PROJECT_DB_OK &&
lardon3d_project_db_schema_version(database) == 18); lardon3d_project_db_schema_version(database) == 19);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
database = NULL; database = NULL;
CHECK(query_integer( CHECK(query_integer(
v13_path, "SELECT value FROM metadata WHERE key='schema_version'", 18)); v13_path, "SELECT value FROM metadata WHERE key='schema_version'", 19));
CHECK( CHECK(
query_integer(v13_path, query_integer(v13_path,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND " "SELECT count(*) FROM sqlite_master WHERE type='table' AND "
@ -1624,7 +1762,7 @@ static bool run_test(void) {
true_v14_path); true_v14_path);
} }
CHECK(true_v14_result == LARDON3D_PROJECT_DB_OK && CHECK(true_v14_result == LARDON3D_PROJECT_DB_OK &&
lardon3d_project_db_schema_version(database) == 18); lardon3d_project_db_schema_version(database) == 19);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
database = NULL; database = NULL;
CHECK( CHECK(
@ -1739,7 +1877,7 @@ static bool run_test(void) {
if (true_v15_result != LARDON3D_PROJECT_DB_OK) if (true_v15_result != LARDON3D_PROJECT_DB_OK)
fprintf(stderr, "true v15 upgrade: %d %s\n", true_v15_result, error); fprintf(stderr, "true v15 upgrade: %d %s\n", true_v15_result, error);
CHECK(true_v15_result == LARDON3D_PROJECT_DB_OK); CHECK(true_v15_result == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 18); CHECK(lardon3d_project_db_schema_version(database) == 19);
Lardon3DProjectDbProject migrated_project; Lardon3DProjectDbProject migrated_project;
CHECK(lardon3d_project_db_get_project(database, &migrated_project) == CHECK(lardon3d_project_db_get_project(database, &migrated_project) ==
LARDON3D_PROJECT_DB_OK && LARDON3D_PROJECT_DB_OK &&
@ -1757,6 +1895,20 @@ static bool run_test(void) {
".lardon3d/checkpoints/legacy.scope") == 0); ".lardon3d/checkpoints/legacy.scope") == 0);
CHECK(query_integer(true_v15_path, "SELECT count(*) FROM scansets", 1)); CHECK(query_integer(true_v15_path, "SELECT count(*) FROM scansets", 1));
CHECK(query_integer(true_v15_path, "SELECT count(*) FROM images", 2)); CHECK(query_integer(true_v15_path, "SELECT count(*) FROM images", 2));
CHECK(query_integer(true_v15_path, "SELECT count(*) FROM captures", 2));
CHECK(query_integer(true_v15_path, "SELECT count(*) FROM capture_images", 2));
CHECK(query_integer(true_v15_path, "SELECT count(*) FROM capture_assets WHERE role=1", 2));
CHECK(query_integer(true_v15_path, "SELECT count(*) FROM capture_selections", 2));
CHECK(query_integer(
true_v15_path,
"SELECT count(*) FROM captures c JOIN capture_images ci ON ci.capture_id=c.capture_id "
"JOIN images i ON i.image_id=ci.image_id WHERE c.scanset_id=i.scanset_id "
"AND c.capture_id=i.image_id",
2));
Lardon3DProjectDbCapture post_migration_capture;
CHECK(lardon3d_project_db_create_capture(database, 1, 2, &post_migration_capture) ==
LARDON3D_PROJECT_DB_OK);
CHECK(post_migration_capture.capture_id > 2);
CHECK(query_integer(true_v15_path, "SELECT count(*) FROM feature_sets", 2)); CHECK(query_integer(true_v15_path, "SELECT count(*) FROM feature_sets", 2));
CHECK(query_integer(true_v15_path, "SELECT count(*) FROM track_sets", 1)); CHECK(query_integer(true_v15_path, "SELECT count(*) FROM track_sets", 1));
CHECK(query_integer(true_v15_path, "SELECT count(*) FROM tracks", 1)); CHECK(query_integer(true_v15_path, "SELECT count(*) FROM tracks", 1));
@ -1771,7 +1923,7 @@ static bool run_test(void) {
database = NULL; database = NULL;
CHECK(lardon3d_project_db_open(database_path, &database, error) == CHECK(lardon3d_project_db_open(database_path, &database, error) ==
LARDON3D_PROJECT_DB_OK); LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 18); CHECK(lardon3d_project_db_schema_version(database) == 19);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
database = NULL; database = NULL;
CHECK(schema_compare(database_path, true_v15_path, true)); CHECK(schema_compare(database_path, true_v15_path, true));
@ -1806,7 +1958,7 @@ static bool run_test(void) {
0)); 0));
CHECK(lardon3d_project_db_open(true_v15_path, &database, error) == CHECK(lardon3d_project_db_open(true_v15_path, &database, error) ==
LARDON3D_PROJECT_DB_OK); LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 18); CHECK(lardon3d_project_db_schema_version(database) == 19);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
database = NULL; database = NULL;
@ -1825,7 +1977,7 @@ static bool run_test(void) {
0)); 0));
CHECK(lardon3d_project_db_open(failed_v14_path, &database, error) == CHECK(lardon3d_project_db_open(failed_v14_path, &database, error) ==
LARDON3D_PROJECT_DB_OK && LARDON3D_PROJECT_DB_OK &&
lardon3d_project_db_schema_version(database) == 18); lardon3d_project_db_schema_version(database) == 19);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
database = NULL; database = NULL;
@ -1844,7 +1996,7 @@ static bool run_test(void) {
0)); 0));
CHECK(lardon3d_project_db_open(failed_v15_path, &database, error) == CHECK(lardon3d_project_db_open(failed_v15_path, &database, error) ==
LARDON3D_PROJECT_DB_OK && LARDON3D_PROJECT_DB_OK &&
lardon3d_project_db_schema_version(database) == 18); lardon3d_project_db_schema_version(database) == 19);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
database = NULL; database = NULL;
@ -2315,6 +2467,7 @@ static bool run_test(void) {
CHECK(unlink(failed_v15_path) == 0); CHECK(unlink(failed_v15_path) == 0);
CHECK(unlink(v17_path) == 0); CHECK(unlink(v17_path) == 0);
CHECK(unlink(failed_v18_path) == 0); CHECK(unlink(failed_v18_path) == 0);
CHECK(unlink(failed_v19_path) == 0);
CHECK(rmdir(directory) == 0); CHECK(rmdir(directory) == 0);
return true; return true;
} }

View file

@ -123,7 +123,7 @@ static bool run_test(void) {
Lardon3DProjectDb *db = NULL; Lardon3DProjectDb *db = NULL;
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]; char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK); CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(db) == 18); CHECK(lardon3d_project_db_schema_version(db) == 19);
Lardon3DProjectDbScanSet scanset; Lardon3DProjectDbScanSet scanset;
CHECK(lardon3d_project_db_create_scanset(db, "Sparse model", &scanset) == CHECK(lardon3d_project_db_create_scanset(db, "Sparse model", &scanset) ==

View file

@ -66,7 +66,7 @@ int main() {
Lardon3DProjectDb *database = nullptr; Lardon3DProjectDb *database = nullptr;
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]{}; char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]{};
CHECK(lardon3d_project_db_open(path.c_str(), &database, error) == LARDON3D_PROJECT_DB_OK); CHECK(lardon3d_project_db_open(path.c_str(), &database, error) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 18); CHECK(lardon3d_project_db_schema_version(database) == 19);
sqlite3 *raw = nullptr; sqlite3 *raw = nullptr;
CHECK(sqlite3_open(path.c_str(), &raw) == SQLITE_OK); CHECK(sqlite3_open(path.c_str(), &raw) == SQLITE_OK);