4397 lines
213 KiB
C
4397 lines
213 KiB
C
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <math.h>
|
|
#include <pthread.h>
|
|
#include <sqlite3.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <lardon3d/project_db.h>
|
|
|
|
struct Lardon3DProjectDb {
|
|
sqlite3 *connection;
|
|
pthread_mutex_t mutex;
|
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
|
};
|
|
|
|
static const char schema_v5[] =
|
|
"CREATE TABLE metadata(key TEXT PRIMARY KEY,value INTEGER NOT NULL);"
|
|
"INSERT INTO metadata(key,value) VALUES('schema_version',5);"
|
|
"INSERT INTO metadata(key,value) VALUES('next_task_id',1);"
|
|
"INSERT INTO metadata(key,value) VALUES('legacy_image_catalog_pending',0);"
|
|
"CREATE TABLE project(singleton INTEGER PRIMARY KEY CHECK(singleton=1),"
|
|
"stable_id TEXT NOT NULL UNIQUE,name TEXT NOT NULL,created_at INTEGER NOT NULL,updated_at "
|
|
"INTEGER NOT NULL);"
|
|
"CREATE TABLE tasks(task_id INTEGER PRIMARY KEY CHECK(task_id>0),name TEXT NOT NULL,"
|
|
"task_kind TEXT,task_kind_version INTEGER,"
|
|
"saved_state INTEGER NOT NULL CHECK(saved_state BETWEEN 0 AND 5),"
|
|
"recovery_state INTEGER NOT NULL CHECK(recovery_state BETWEEN 0 AND 5),"
|
|
"progress INTEGER NOT NULL CHECK(progress BETWEEN 0 AND 100),sequence_count INTEGER NOT NULL "
|
|
"CHECK(sequence_count>=0),"
|
|
"started_sec INTEGER NOT NULL,started_nsec INTEGER NOT NULL CHECK(started_nsec BETWEEN 0 AND "
|
|
"999999999),"
|
|
"finished_sec INTEGER NOT NULL,finished_nsec INTEGER NOT NULL CHECK(finished_nsec BETWEEN 0 "
|
|
"AND 999999999),updated_at INTEGER NOT NULL,"
|
|
"CHECK((task_kind IS NULL AND task_kind_version IS NULL) OR (task_kind IS NOT NULL AND "
|
|
"task_kind_version>0)));"
|
|
"CREATE INDEX tasks_recovery_state_idx ON tasks(recovery_state,task_id);"
|
|
"CREATE TABLE checkpoints(task_id INTEGER PRIMARY KEY REFERENCES tasks(task_id) ON DELETE "
|
|
"CASCADE,"
|
|
"path TEXT NOT NULL,format_version INTEGER NOT NULL CHECK(format_version>0),"
|
|
"durability INTEGER NOT NULL CHECK(durability BETWEEN 0 AND 1),updated_at INTEGER NOT NULL);"
|
|
"CREATE TABLE artifacts(artifact_id TEXT PRIMARY KEY,kind TEXT NOT NULL,path TEXT NOT NULL,"
|
|
"state INTEGER NOT NULL CHECK(state BETWEEN 0 AND 1),size_bytes INTEGER NOT NULL "
|
|
"CHECK(size_bytes>=0),"
|
|
"producer_task_id INTEGER REFERENCES tasks(task_id),created_at INTEGER NOT NULL,updated_at "
|
|
"INTEGER NOT NULL);"
|
|
"CREATE INDEX artifacts_state_idx ON artifacts(state,artifact_id);"
|
|
"CREATE INDEX artifacts_producer_idx ON artifacts(producer_task_id);"
|
|
"CREATE TABLE scansets(scanset_id INTEGER PRIMARY KEY AUTOINCREMENT CHECK(scanset_id>0),"
|
|
"name TEXT NOT NULL CHECK(length(name)>0 AND length(name)<256),created_at INTEGER NOT NULL "
|
|
"CHECK(created_at>=0),"
|
|
"updated_at INTEGER NOT NULL CHECK(updated_at>=created_at));"
|
|
"CREATE TABLE image_assets(asset_id INTEGER PRIMARY KEY AUTOINCREMENT CHECK(asset_id>0),sha256 "
|
|
"BLOB NOT NULL UNIQUE CHECK(length(sha256)=32),"
|
|
"path TEXT NOT NULL UNIQUE CHECK(length(path)>0 AND length(path)<4096),size_bytes INTEGER NOT "
|
|
"NULL CHECK(size_bytes>=0),"
|
|
"state INTEGER NOT NULL CHECK(state=1),created_at INTEGER NOT NULL CHECK(created_at>=0));"
|
|
"CREATE TABLE images(image_id INTEGER PRIMARY KEY AUTOINCREMENT CHECK(image_id>0),scanset_id "
|
|
"INTEGER NOT NULL REFERENCES scansets(scanset_id),"
|
|
"asset_id INTEGER NOT NULL REFERENCES image_assets(asset_id),original_name TEXT NOT NULL "
|
|
"CHECK(length(original_name)>0 AND length(original_name)<256),"
|
|
"source_path TEXT NOT NULL CHECK(length(source_path)>0 AND length(source_path)<4096),"
|
|
"producer_task_id INTEGER REFERENCES tasks(task_id),imported_at INTEGER NOT NULL "
|
|
"CHECK(imported_at>=0),"
|
|
"UNIQUE(scanset_id,asset_id));"
|
|
"CREATE INDEX images_scanset_idx ON images(scanset_id,image_id);"
|
|
"CREATE INDEX images_producer_idx ON images(producer_task_id,image_id);"
|
|
"CREATE TABLE image_import_tasks(task_id INTEGER PRIMARY KEY REFERENCES tasks(task_id) ON "
|
|
"DELETE CASCADE,"
|
|
"source_path TEXT NOT NULL,scanset_id INTEGER NOT NULL REFERENCES scansets(scanset_id));";
|
|
|
|
static const char schema_feature_v5[] =
|
|
"CREATE TABLE feature_assets(feature_asset_id INTEGER PRIMARY KEY AUTOINCREMENT "
|
|
"CHECK(feature_asset_id>0),sha256 BLOB NOT NULL UNIQUE CHECK(length(sha256)=32),path TEXT NOT "
|
|
"NULL UNIQUE CHECK(length(path)>0 AND length(path)<4096),size_bytes INTEGER NOT NULL "
|
|
"CHECK(size_bytes>=0),durability INTEGER NOT NULL CHECK(durability BETWEEN 0 AND 1),created_at "
|
|
"INTEGER NOT NULL CHECK(created_at>=0));"
|
|
"CREATE TABLE feature_sets(feature_set_id INTEGER PRIMARY KEY AUTOINCREMENT "
|
|
"CHECK(feature_set_id>0),image_id INTEGER NOT NULL REFERENCES "
|
|
"images(image_id),feature_asset_id INTEGER NOT NULL REFERENCES "
|
|
"feature_assets(feature_asset_id),extractor_kind TEXT NOT NULL CHECK(length(extractor_kind)>0 "
|
|
"AND length(extractor_kind)<65),extractor_version INTEGER NOT NULL "
|
|
"CHECK(extractor_version>0),parameter_fingerprint BLOB NOT NULL "
|
|
"CHECK(length(parameter_fingerprint)=32),source_image_sha256 BLOB NOT NULL "
|
|
"CHECK(length(source_image_sha256)=32),feature_count INTEGER NOT NULL "
|
|
"CHECK(feature_count BETWEEN 0 AND 8192),descriptor_type INTEGER NOT NULL "
|
|
"CHECK(descriptor_type "
|
|
"IN(1,2)),descriptor_dimension INTEGER NOT NULL "
|
|
"CHECK(descriptor_dimension BETWEEN 1 AND 4096),producer_task_id "
|
|
"INTEGER REFERENCES tasks(task_id),created_at INTEGER NOT NULL "
|
|
"CHECK(created_at>=0),UNIQUE(image_id,extractor_kind,extractor_version,parameter_fingerprint));"
|
|
"CREATE INDEX feature_sets_image_idx ON feature_sets(image_id,feature_set_id);"
|
|
"CREATE INDEX feature_sets_producer_idx ON feature_sets(producer_task_id,feature_set_id);"
|
|
"CREATE TABLE feature_extract_tasks(task_id INTEGER PRIMARY KEY REFERENCES tasks(task_id) ON "
|
|
"DELETE CASCADE,image_id INTEGER NOT NULL REFERENCES images(image_id),extractor_kind TEXT NOT "
|
|
"NULL,extractor_version INTEGER NOT NULL CHECK(extractor_version>0),max_features INTEGER NOT "
|
|
"NULL CHECK(max_features BETWEEN 1 AND 8192),pyramid_levels INTEGER NOT NULL "
|
|
"CHECK(pyramid_levels BETWEEN 1 AND 16),fast_threshold INTEGER NOT NULL "
|
|
"CHECK(fast_threshold BETWEEN 1 AND 255),parameter_fingerprint BLOB NOT NULL "
|
|
"CHECK(length(parameter_fingerprint)=32));";
|
|
|
|
static const char schema_visual_v6[] =
|
|
"CREATE TABLE visual_indexes(visual_index_id INTEGER PRIMARY KEY AUTOINCREMENT "
|
|
"CHECK(visual_index_id>0),index_kind TEXT NOT NULL CHECK(length(index_kind)>0 AND "
|
|
"length(index_kind)<65),index_version INTEGER NOT NULL CHECK(index_version>0),"
|
|
"descriptor_type INTEGER NOT NULL CHECK(descriptor_type IN(1,2)),descriptor_dimension "
|
|
"INTEGER NOT NULL CHECK(descriptor_dimension BETWEEN 1 AND 4096),extractor_kind TEXT NOT "
|
|
"NULL CHECK(length(extractor_kind)>0 AND length(extractor_kind)<65),extractor_version "
|
|
"INTEGER NOT NULL CHECK(extractor_version>0),feature_parameter_fingerprint BLOB NOT NULL "
|
|
"CHECK(length(feature_parameter_fingerprint)=32),index_parameter_fingerprint BLOB NOT NULL "
|
|
"CHECK(length(index_parameter_fingerprint)=32),table_count INTEGER NOT NULL CHECK(table_count "
|
|
"BETWEEN 1 AND 32),key_bits INTEGER NOT NULL CHECK(key_bits BETWEEN 8 AND 32),"
|
|
"max_features_per_set INTEGER NOT NULL CHECK(max_features_per_set BETWEEN 1 AND 1024),"
|
|
"max_bucket_postings INTEGER NOT NULL CHECK(max_bucket_postings BETWEEN 1 AND 4096),"
|
|
"created_at INTEGER NOT NULL CHECK(created_at>=0),UNIQUE(index_kind,index_version,"
|
|
"descriptor_type,descriptor_dimension,extractor_kind,extractor_version,"
|
|
"feature_parameter_fingerprint,index_parameter_fingerprint));"
|
|
"CREATE TABLE visual_index_segments(visual_index_segment_id INTEGER PRIMARY KEY "
|
|
"AUTOINCREMENT CHECK(visual_index_segment_id>0),visual_index_id INTEGER NOT NULL REFERENCES "
|
|
"visual_indexes(visual_index_id),generation INTEGER NOT NULL CHECK(generation>0),sha256 BLOB "
|
|
"NOT NULL CHECK(length(sha256)=32),path TEXT NOT NULL UNIQUE CHECK(length(path)>0 AND "
|
|
"length(path)<4096),size_bytes INTEGER NOT NULL CHECK(size_bytes>=128),posting_count INTEGER "
|
|
"NOT NULL CHECK(posting_count>=0),feature_set_count INTEGER NOT NULL CHECK(feature_set_count "
|
|
"BETWEEN 1 AND 16),durability INTEGER NOT NULL CHECK(durability BETWEEN 0 AND 1),"
|
|
"producer_task_id INTEGER REFERENCES tasks(task_id),created_at INTEGER NOT NULL "
|
|
"CHECK(created_at>=0),UNIQUE(visual_index_id,generation),UNIQUE(visual_index_id,sha256));"
|
|
"CREATE INDEX visual_index_segments_index_idx ON visual_index_segments(visual_index_id,"
|
|
"generation);"
|
|
"CREATE TABLE visual_index_memberships(visual_index_id INTEGER NOT NULL REFERENCES "
|
|
"visual_indexes(visual_index_id),feature_set_id INTEGER NOT NULL REFERENCES "
|
|
"feature_sets(feature_set_id),visual_index_segment_id INTEGER NOT NULL REFERENCES "
|
|
"visual_index_segments(visual_index_segment_id),PRIMARY KEY(visual_index_id,feature_set_id));"
|
|
"CREATE INDEX visual_index_memberships_segment_idx ON visual_index_memberships("
|
|
"visual_index_segment_id,feature_set_id);"
|
|
"CREATE TABLE visual_index_update_tasks(task_id INTEGER PRIMARY KEY REFERENCES tasks(task_id) "
|
|
"ON DELETE CASCADE,visual_index_id INTEGER NOT NULL REFERENCES visual_indexes(visual_index_id),"
|
|
"after_feature_set_id INTEGER NOT NULL CHECK(after_feature_set_id>=0));";
|
|
|
|
static const char schema_precision_v7[] =
|
|
"ALTER TABLE feature_sets ADD COLUMN occupied_cells INTEGER NOT NULL DEFAULT 0 "
|
|
"CHECK(occupied_cells>=0 AND occupied_cells<=1024);"
|
|
"ALTER TABLE feature_sets ADD COLUMN total_cells INTEGER NOT NULL DEFAULT 0 "
|
|
"CHECK(total_cells>=0 AND total_cells<=1024 AND occupied_cells<=total_cells);"
|
|
"ALTER TABLE feature_sets ADD COLUMN coverage_ratio REAL NOT NULL DEFAULT 0.0 "
|
|
"CHECK(coverage_ratio>=0.0 AND coverage_ratio<=1.0);"
|
|
"ALTER TABLE feature_sets ADD COLUMN feature_density_per_megapixel REAL NOT NULL DEFAULT 0.0 "
|
|
"CHECK(feature_density_per_megapixel>=0.0);"
|
|
"CREATE TABLE sift_extract_tasks(task_id INTEGER PRIMARY KEY REFERENCES tasks(task_id) ON "
|
|
"DELETE CASCADE,image_id INTEGER NOT NULL REFERENCES images(image_id),extractor_kind TEXT NOT "
|
|
"NULL CHECK(extractor_kind IN('sift','rootsift')),extractor_version INTEGER NOT NULL "
|
|
"CHECK(extractor_version=1),max_features INTEGER NOT NULL CHECK(max_features BETWEEN 1 AND "
|
|
"8192),octave_layers INTEGER NOT NULL CHECK(octave_layers BETWEEN 1 AND 8),"
|
|
"contrast_threshold REAL NOT NULL CHECK(contrast_threshold BETWEEN 0.001 AND 0.2),"
|
|
"edge_threshold REAL NOT NULL CHECK(edge_threshold BETWEEN 1.0 AND 100.0),sigma REAL NOT NULL "
|
|
"CHECK(sigma BETWEEN 0.5 AND 3.0),grid_rows INTEGER NOT NULL CHECK(grid_rows BETWEEN 1 AND 32),"
|
|
"grid_cols INTEGER NOT NULL CHECK(grid_cols BETWEEN 1 AND 32),max_features_per_cell INTEGER "
|
|
"NOT NULL CHECK(max_features_per_cell BETWEEN 1 AND 8192),parameter_fingerprint BLOB NOT NULL "
|
|
"CHECK(length(parameter_fingerprint)=32));"
|
|
"CREATE TABLE feature_support_sets(feature_support_set_id INTEGER PRIMARY KEY AUTOINCREMENT "
|
|
"CHECK(feature_support_set_id>0),image_id INTEGER NOT NULL REFERENCES images(image_id),"
|
|
"first_feature_set_id INTEGER NOT NULL REFERENCES feature_sets(feature_set_id),"
|
|
"second_feature_set_id INTEGER NOT NULL REFERENCES feature_sets(feature_set_id),radius_pixels "
|
|
"REAL NOT NULL CHECK(radius_pixels>0.0 AND radius_pixels<=64.0),parameter_fingerprint BLOB NOT "
|
|
"NULL CHECK(length(parameter_fingerprint)=32),created_at INTEGER NOT NULL CHECK(created_at>=0),"
|
|
"UNIQUE(image_id,first_feature_set_id,second_feature_set_id,parameter_fingerprint));"
|
|
"CREATE TABLE feature_support_groups(feature_support_group_id INTEGER PRIMARY KEY AUTOINCREMENT "
|
|
"CHECK(feature_support_group_id>0),feature_support_set_id INTEGER NOT NULL REFERENCES "
|
|
"feature_support_sets(feature_support_set_id) ON DELETE CASCADE,x REAL NOT NULL,y REAL NOT NULL,"
|
|
"distance_pixels REAL NOT NULL CHECK(distance_pixels>=0.0 AND distance_pixels<=64.0),"
|
|
"support_count INTEGER NOT NULL CHECK(support_count BETWEEN 1 AND 3));"
|
|
"CREATE INDEX feature_support_groups_set_idx ON feature_support_groups(feature_support_set_id,"
|
|
"feature_support_group_id);"
|
|
"CREATE TABLE feature_support_members(feature_support_group_id INTEGER NOT NULL REFERENCES "
|
|
"feature_support_groups(feature_support_group_id) ON DELETE CASCADE,feature_set_id INTEGER NOT "
|
|
"NULL REFERENCES feature_sets(feature_set_id),feature_index INTEGER NOT NULL CHECK(feature_index "
|
|
"BETWEEN 0 AND 8191),extractor_kind TEXT NOT NULL CHECK(length(extractor_kind)>0 AND "
|
|
"length(extractor_kind)<65),PRIMARY KEY(feature_support_group_id,feature_set_id));";
|
|
|
|
static const char schema_candidate_v8[] =
|
|
"CREATE TABLE candidate_pairs(candidate_pair_id INTEGER PRIMARY KEY AUTOINCREMENT "
|
|
"CHECK(candidate_pair_id>0),image_id_a INTEGER NOT NULL REFERENCES images(image_id),"
|
|
"image_id_b INTEGER NOT NULL REFERENCES images(image_id),created_at INTEGER NOT NULL "
|
|
"CHECK(created_at>=0),CHECK(image_id_a<image_id_b),UNIQUE(image_id_a,image_id_b));"
|
|
"CREATE INDEX candidate_pairs_image_a_idx ON candidate_pairs(image_id_a);"
|
|
"CREATE INDEX candidate_pairs_image_b_idx ON candidate_pairs(image_id_b);";
|
|
|
|
static const char schema_candidate_generate_v9[] =
|
|
"CREATE TABLE candidate_pair_generate_tasks(task_id INTEGER PRIMARY KEY REFERENCES "
|
|
"tasks(task_id) ON DELETE CASCADE,"
|
|
"visual_index_id INTEGER NOT NULL CHECK(visual_index_id>0),"
|
|
"after_feature_set_id INTEGER NOT NULL CHECK(after_feature_set_id>=0),"
|
|
"top_k INTEGER NOT NULL CHECK(top_k>0 AND top_k<=256),"
|
|
"minimum_evidence_count INTEGER NOT NULL CHECK(minimum_evidence_count>=0 "
|
|
"AND minimum_evidence_count<=1024),"
|
|
"scanset_filter INTEGER NOT NULL CHECK(scanset_filter>=0 AND scanset_filter<=2),"
|
|
"exclude_same_asset INTEGER NOT NULL CHECK(exclude_same_asset IN (0,1)));";
|
|
|
|
static const char schema_match_result_v10[] =
|
|
"CREATE TABLE match_results("
|
|
"match_result_id INTEGER PRIMARY KEY AUTOINCREMENT CHECK(match_result_id>0),"
|
|
"candidate_pair_id INTEGER NOT NULL REFERENCES candidate_pairs(candidate_pair_id),"
|
|
"feature_set_id_a INTEGER NOT NULL REFERENCES feature_sets(feature_set_id),"
|
|
"feature_set_id_b INTEGER NOT NULL REFERENCES feature_sets(feature_set_id),"
|
|
"matcher_kind TEXT NOT NULL CHECK(length(matcher_kind)>0 AND length(matcher_kind)<=64),"
|
|
"matcher_version INTEGER NOT NULL CHECK(matcher_version>0),"
|
|
"parameter_fingerprint BLOB NOT NULL CHECK(length(parameter_fingerprint)=32),"
|
|
"result_status INTEGER NOT NULL CHECK(result_status IN (0,1)),"
|
|
"match_count INTEGER NOT NULL CHECK(match_count>=0 AND match_count<=8192),"
|
|
"match_asset_sha256 BLOB CHECK(match_asset_sha256 IS NULL OR"
|
|
" length(match_asset_sha256)=32),"
|
|
"match_asset_path TEXT CHECK(match_asset_path IS NULL OR length(match_asset_path)>0),"
|
|
"match_asset_size_bytes INTEGER CHECK(match_asset_size_bytes IS NULL OR"
|
|
" match_asset_size_bytes>0),"
|
|
"created_at INTEGER NOT NULL CHECK(created_at>=0),"
|
|
"CHECK((result_status=0 AND match_count=0 AND match_asset_sha256 IS NULL AND "
|
|
"match_asset_path IS NULL AND match_asset_size_bytes IS NULL) OR "
|
|
"(result_status=1 AND match_count>0 AND match_asset_sha256 IS NOT NULL AND "
|
|
"match_asset_path IS NOT NULL AND match_asset_size_bytes IS NOT NULL)),"
|
|
"UNIQUE(candidate_pair_id,feature_set_id_a,feature_set_id_b,"
|
|
"matcher_kind,matcher_version,parameter_fingerprint));"
|
|
"CREATE INDEX match_results_candidate_pair_idx ON match_results(candidate_pair_id);"
|
|
"CREATE INDEX match_results_feature_set_a_idx ON match_results(feature_set_id_a);"
|
|
"CREATE INDEX match_results_feature_set_b_idx ON match_results(feature_set_id_b);";
|
|
|
|
static const char schema_matcher_task_v11[] =
|
|
"CREATE TABLE matcher_tasks("
|
|
"task_id INTEGER PRIMARY KEY REFERENCES tasks(task_id) ON DELETE CASCADE,"
|
|
"after_candidate_pair_id INTEGER NOT NULL CHECK(after_candidate_pair_id>=0),"
|
|
"feature_extractor_kind TEXT NOT NULL CHECK(length(feature_extractor_kind)>0 AND "
|
|
"length(feature_extractor_kind)<65),"
|
|
"feature_extractor_version INTEGER NOT NULL CHECK(feature_extractor_version>0),"
|
|
"feature_parameter_fingerprint BLOB NOT NULL "
|
|
"CHECK(length(feature_parameter_fingerprint)=32),"
|
|
"matcher_kind INTEGER NOT NULL CHECK(matcher_kind BETWEEN 0 AND 2),"
|
|
"ratio_threshold REAL NOT NULL CHECK(ratio_threshold>0.0 AND ratio_threshold<1.0));";
|
|
|
|
static const char schema_geometric_verification_v12[] =
|
|
"CREATE TABLE geometric_verification_results("
|
|
"geometric_verification_result_id INTEGER PRIMARY KEY AUTOINCREMENT "
|
|
"CHECK(geometric_verification_result_id>0),"
|
|
"match_result_id INTEGER NOT NULL REFERENCES match_results(match_result_id) ON DELETE CASCADE,"
|
|
"verifier_kind INTEGER NOT NULL CHECK(verifier_kind=1),"
|
|
"verifier_version INTEGER NOT NULL CHECK(verifier_version>0 AND verifier_version<=4294967295),"
|
|
"parameter_fingerprint BLOB NOT NULL CHECK(length(parameter_fingerprint)=32),"
|
|
"status INTEGER NOT NULL CHECK(status IN (1,2)),"
|
|
"inlier_count INTEGER NOT NULL CHECK(inlier_count>=0 AND inlier_count<=8192),"
|
|
"inlier_mask BLOB NOT NULL CHECK(length(inlier_mask)>=1 AND length(inlier_mask)<=1024),"
|
|
"model_m00 REAL,model_m01 REAL,model_m02 REAL,"
|
|
"model_m10 REAL,model_m11 REAL,model_m12 REAL,"
|
|
"model_m20 REAL,model_m21 REAL,model_m22 REAL,"
|
|
"created_at INTEGER NOT NULL CHECK(created_at>=0),"
|
|
"CHECK((status=1 AND model_m00 IS NULL AND model_m01 IS NULL AND model_m02 IS NULL AND "
|
|
"model_m10 IS NULL AND model_m11 IS NULL AND model_m12 IS NULL AND model_m20 IS NULL AND "
|
|
"model_m21 IS NULL AND model_m22 IS NULL) OR "
|
|
"(status=2 AND model_m00 IS NOT NULL AND model_m01 IS NOT NULL AND model_m02 IS NOT NULL AND "
|
|
"model_m10 IS NOT NULL AND model_m11 IS NOT NULL AND model_m12 IS NOT NULL AND "
|
|
"model_m20 IS NOT NULL AND model_m21 IS NOT NULL AND model_m22 IS NOT NULL)),"
|
|
"UNIQUE(match_result_id,verifier_kind,verifier_version,parameter_fingerprint));"
|
|
"CREATE INDEX geometric_verification_results_parent_idx ON "
|
|
"geometric_verification_results(match_result_id,geometric_verification_result_id);";
|
|
|
|
static const char schema_geometric_verifier_task_v13[] =
|
|
"CREATE TABLE geometric_verifier_tasks("
|
|
"task_id INTEGER PRIMARY KEY REFERENCES tasks(task_id) ON DELETE CASCADE,"
|
|
"after_match_result_id INTEGER NOT NULL CHECK(after_match_result_id>=0),"
|
|
"threshold_pixels REAL NOT NULL CHECK(threshold_pixels>0.0),"
|
|
"confidence REAL NOT NULL CHECK(confidence>0.0 AND confidence<1.0),"
|
|
"max_iterations INTEGER NOT NULL CHECK(max_iterations>0 AND max_iterations<=2147483647),"
|
|
"min_inlier_count INTEGER NOT NULL CHECK(min_inlier_count BETWEEN 1 AND 8192),"
|
|
"min_inlier_ratio REAL NOT NULL CHECK(min_inlier_ratio>=0.0 AND min_inlier_ratio<=1.0),"
|
|
"seed_policy_version INTEGER NOT NULL CHECK(seed_policy_version>0),"
|
|
"canonicalization_version INTEGER NOT NULL CHECK(canonicalization_version>0),"
|
|
"parameter_fingerprint BLOB NOT NULL CHECK(length(parameter_fingerprint)=32));";
|
|
|
|
static void copy_error(char destination[LARDON3D_PROJECT_DB_ERROR_CAPACITY], const char *text) {
|
|
if (destination) {
|
|
(void)snprintf(destination, LARDON3D_PROJECT_DB_ERROR_CAPACITY, "%s", text ? text : "");
|
|
}
|
|
}
|
|
|
|
static Lardon3DProjectDbResult sqlite_result(Lardon3DProjectDb *database, int code,
|
|
const char *context) {
|
|
(void)snprintf(database->error, sizeof(database->error), "%s: %s", context,
|
|
sqlite3_errmsg(database->connection));
|
|
if (code == SQLITE_BUSY || code == SQLITE_LOCKED) {
|
|
return LARDON3D_PROJECT_DB_BUSY;
|
|
}
|
|
if (code == SQLITE_CONSTRAINT) {
|
|
return LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
}
|
|
if (code == SQLITE_CORRUPT || code == SQLITE_NOTADB) {
|
|
return LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
return LARDON3D_PROJECT_DB_IO_ERROR;
|
|
}
|
|
|
|
static bool bounded_text(const char *text, size_t capacity, bool allow_empty) {
|
|
if (!text) {
|
|
return false;
|
|
}
|
|
size_t length = strnlen(text, capacity);
|
|
return length < capacity && (allow_empty || length > 0);
|
|
}
|
|
|
|
static bool valid_task_id(uint64_t id) { return id > 0 && id <= INT64_MAX; }
|
|
|
|
static bool valid_state(Lardon3DTaskState state) {
|
|
return state >= TASK_PENDING && state <= TASK_COMPLETED;
|
|
}
|
|
|
|
static bool database_time(sqlite3_int64 value, time_t *output) {
|
|
if (value < 0) {
|
|
return false;
|
|
}
|
|
time_t converted = (time_t)value;
|
|
if (converted < 0 || (uint64_t)converted != (uint64_t)value) {
|
|
return false;
|
|
}
|
|
*output = converted;
|
|
return true;
|
|
}
|
|
|
|
static Lardon3DProjectDbResult execute(Lardon3DProjectDb *database, const char *sql,
|
|
const char *context) {
|
|
char *message = NULL;
|
|
int code = sqlite3_exec(database->connection, sql, NULL, NULL, &message);
|
|
if (code == SQLITE_OK) {
|
|
return LARDON3D_PROJECT_DB_OK;
|
|
}
|
|
(void)snprintf(database->error, sizeof(database->error), "%s: %s", context,
|
|
message ? message : sqlite3_errmsg(database->connection));
|
|
sqlite3_free(message);
|
|
return sqlite_result(database, code, context);
|
|
}
|
|
|
|
static Lardon3DProjectDbResult prepare(Lardon3DProjectDb *database, const char *sql,
|
|
sqlite3_stmt **statement) {
|
|
int code = sqlite3_prepare_v2(database->connection, sql, -1, statement, NULL);
|
|
return code == SQLITE_OK ? LARDON3D_PROJECT_DB_OK : sqlite_result(database, code, "prepare");
|
|
}
|
|
|
|
static bool table_exists(sqlite3 *connection, const char *name) {
|
|
sqlite3_stmt *statement = NULL;
|
|
if (sqlite3_prepare_v2(connection, "SELECT 1 FROM sqlite_master WHERE type='table' AND name=?1",
|
|
-1, &statement, NULL) != SQLITE_OK) {
|
|
return false;
|
|
}
|
|
(void)sqlite3_bind_text(statement, 1, name, -1, SQLITE_STATIC);
|
|
bool exists = sqlite3_step(statement) == SQLITE_ROW;
|
|
(void)sqlite3_finalize(statement);
|
|
return exists;
|
|
}
|
|
|
|
static Lardon3DProjectDbResult migrate(Lardon3DProjectDb *database, unsigned int from_version) {
|
|
if (from_version > LARDON3D_PROJECT_DB_SCHEMA_VERSION) {
|
|
copy_error(database->error, "Version de schéma future non supportée.");
|
|
return LARDON3D_PROJECT_DB_UNSUPPORTED_SCHEMA;
|
|
}
|
|
if (from_version == LARDON3D_PROJECT_DB_SCHEMA_VERSION) {
|
|
return LARDON3D_PROJECT_DB_OK;
|
|
}
|
|
if (from_version != 0 && from_version != 1 && from_version != 2 && from_version != 3 &&
|
|
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 != 12) {
|
|
return LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin migration");
|
|
if (result == LARDON3D_PROJECT_DB_OK && from_version == 0) {
|
|
result = execute(database, schema_v5, "create schema v5");
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = execute(database, schema_feature_v5, "create feature schema v5");
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = execute(database, schema_visual_v6, "create visual index schema v6");
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = execute(database,
|
|
"UPDATE metadata SET value=6 WHERE key='schema_version' AND value=5",
|
|
"finish new schema v6");
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && from_version == 1) {
|
|
result = execute(database,
|
|
"ALTER TABLE tasks ADD COLUMN task_kind TEXT;"
|
|
"ALTER TABLE tasks ADD COLUMN task_kind_version INTEGER "
|
|
"CHECK(task_kind_version IS NULL OR task_kind_version>0)",
|
|
"migrate schema v1 to v2");
|
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
|
const char *forced_failure = getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V2");
|
|
if (result == LARDON3D_PROJECT_DB_OK && forced_failure && strcmp(forced_failure, "1") == 0) {
|
|
result =
|
|
execute(database, "INSERT INTO missing_test_table VALUES(1)", "forced migration failure");
|
|
}
|
|
#endif
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result =
|
|
execute(database, "UPDATE metadata SET value=2 WHERE key='schema_version' AND value=1",
|
|
"finish schema v2 migration");
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && from_version != 0 && from_version < 3) {
|
|
result = execute(database,
|
|
"CREATE TABLE image_import_tasks(task_id INTEGER PRIMARY KEY REFERENCES "
|
|
"tasks(task_id) ON DELETE CASCADE,source_path TEXT NOT NULL);"
|
|
"INSERT INTO metadata(key,value) VALUES('next_task_id',(SELECT CASE WHEN "
|
|
"COALESCE(MAX(task_id),0)>=9223372036854775807 THEN 0 ELSE "
|
|
"COALESCE(MAX(task_id),0)+1 END FROM tasks))",
|
|
"migrate schema v2 to v3");
|
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
|
const char *forced_failure = getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V3");
|
|
if (result == LARDON3D_PROJECT_DB_OK && forced_failure && strcmp(forced_failure, "1") == 0) {
|
|
result =
|
|
execute(database, "INSERT INTO missing_test_table VALUES(1)", "forced migration failure");
|
|
}
|
|
#endif
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result =
|
|
execute(database, "UPDATE metadata SET value=3 WHERE key='schema_version' AND value=2",
|
|
"finish schema v3 migration");
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && from_version != 0 && from_version < 4) {
|
|
result = execute(
|
|
database,
|
|
"CREATE TABLE scansets(scanset_id INTEGER PRIMARY KEY AUTOINCREMENT "
|
|
"CHECK(scanset_id>0),name TEXT NOT NULL CHECK(length(name)>0 AND "
|
|
"length(name)<256),created_at INTEGER NOT NULL CHECK(created_at>=0),updated_at INTEGER "
|
|
"NOT NULL CHECK(updated_at>=created_at));"
|
|
"CREATE TABLE image_assets(asset_id INTEGER PRIMARY KEY AUTOINCREMENT "
|
|
"CHECK(asset_id>0),sha256 BLOB NOT NULL UNIQUE CHECK(length(sha256)=32),path TEXT NOT "
|
|
"NULL UNIQUE CHECK(length(path)>0 AND length(path)<4096),size_bytes INTEGER NOT NULL "
|
|
"CHECK(size_bytes>=0),state INTEGER NOT NULL CHECK(state=1),created_at INTEGER NOT "
|
|
"NULL CHECK(created_at>=0));"
|
|
"CREATE TABLE images(image_id INTEGER PRIMARY KEY AUTOINCREMENT "
|
|
"CHECK(image_id>0),scanset_id INTEGER NOT NULL REFERENCES "
|
|
"scansets(scanset_id),asset_id INTEGER NOT NULL REFERENCES "
|
|
"image_assets(asset_id),original_name TEXT NOT NULL CHECK(length(original_name)>0 AND "
|
|
"length(original_name)<256),source_path TEXT NOT NULL CHECK(length(source_path)>0 AND "
|
|
"length(source_path)<4096),producer_task_id INTEGER REFERENCES "
|
|
"tasks(task_id),imported_at INTEGER NOT NULL "
|
|
"CHECK(imported_at>=0),UNIQUE(scanset_id,asset_id));"
|
|
"CREATE INDEX images_scanset_idx ON images(scanset_id,image_id);"
|
|
"CREATE INDEX images_producer_idx ON images(producer_task_id,image_id);"
|
|
"ALTER TABLE image_import_tasks ADD COLUMN scanset_id INTEGER REFERENCES "
|
|
"scansets(scanset_id);"
|
|
"INSERT INTO scansets(name,created_at,updated_at) SELECT 'Imports antérieurs à ScanSet "
|
|
"v1',0,0 WHERE EXISTS(SELECT 1 FROM image_import_tasks);"
|
|
"UPDATE image_import_tasks SET scanset_id=(SELECT scanset_id FROM scansets WHERE "
|
|
"name='Imports antérieurs à ScanSet v1' ORDER BY scanset_id LIMIT 1) WHERE scanset_id "
|
|
"IS NULL;"
|
|
"INSERT INTO metadata(key,value) VALUES('legacy_image_catalog_pending',CASE WHEN "
|
|
"EXISTS(SELECT 1 FROM image_import_tasks) THEN 1 ELSE 0 END)",
|
|
"migrate schema v3 to v4");
|
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
|
const char *forced_failure = getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V4");
|
|
if (result == LARDON3D_PROJECT_DB_OK && forced_failure && strcmp(forced_failure, "1") == 0) {
|
|
result =
|
|
execute(database, "INSERT INTO missing_test_table VALUES(1)", "forced migration failure");
|
|
}
|
|
#endif
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result =
|
|
execute(database, "UPDATE metadata SET value=4 WHERE key='schema_version' AND value=3",
|
|
"finish schema v4 migration");
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && from_version != 0 && from_version < 5) {
|
|
result = execute(
|
|
database,
|
|
"CREATE TABLE feature_assets(feature_asset_id INTEGER PRIMARY KEY AUTOINCREMENT "
|
|
"CHECK(feature_asset_id>0),sha256 BLOB NOT NULL UNIQUE CHECK(length(sha256)=32),path "
|
|
"TEXT NOT NULL UNIQUE CHECK(length(path)>0 AND length(path)<4096),size_bytes INTEGER "
|
|
"NOT NULL CHECK(size_bytes>=0),durability INTEGER NOT NULL CHECK(durability BETWEEN 0 "
|
|
"AND 1),created_at INTEGER NOT NULL CHECK(created_at>=0));"
|
|
"CREATE TABLE feature_sets(feature_set_id INTEGER PRIMARY KEY AUTOINCREMENT "
|
|
"CHECK(feature_set_id>0),image_id INTEGER NOT NULL REFERENCES "
|
|
"images(image_id),feature_asset_id INTEGER NOT NULL REFERENCES "
|
|
"feature_assets(feature_asset_id),extractor_kind TEXT NOT NULL "
|
|
"CHECK(length(extractor_kind)>0 AND length(extractor_kind)<65),extractor_version "
|
|
"INTEGER NOT NULL CHECK(extractor_version>0),parameter_fingerprint BLOB NOT NULL "
|
|
"CHECK(length(parameter_fingerprint)=32),source_image_sha256 BLOB NOT NULL "
|
|
"CHECK(length(source_image_sha256)=32),feature_count INTEGER NOT NULL "
|
|
"CHECK(feature_count BETWEEN 0 AND 8192),descriptor_type INTEGER NOT NULL "
|
|
"CHECK(descriptor_type "
|
|
"IN(1,2)),descriptor_dimension INTEGER NOT NULL "
|
|
"CHECK(descriptor_dimension BETWEEN 1 AND 4096),producer_task_id INTEGER REFERENCES "
|
|
"tasks(task_id),created_at INTEGER NOT NULL "
|
|
"CHECK(created_at>=0),UNIQUE(image_id,extractor_kind,extractor_version,parameter_"
|
|
"fingerprint));"
|
|
"CREATE INDEX feature_sets_image_idx ON feature_sets(image_id,feature_set_id);"
|
|
"CREATE INDEX feature_sets_producer_idx ON "
|
|
"feature_sets(producer_task_id,feature_set_id);"
|
|
"CREATE TABLE feature_extract_tasks(task_id INTEGER PRIMARY KEY REFERENCES "
|
|
"tasks(task_id) ON DELETE CASCADE,image_id INTEGER NOT NULL REFERENCES "
|
|
"images(image_id),extractor_kind TEXT NOT NULL,extractor_version INTEGER NOT NULL "
|
|
"CHECK(extractor_version>0),max_features INTEGER NOT NULL "
|
|
"CHECK(max_features BETWEEN 1 AND 8192),pyramid_levels INTEGER NOT NULL "
|
|
"CHECK(pyramid_levels BETWEEN 1 AND 16),fast_threshold INTEGER NOT NULL "
|
|
"CHECK(fast_threshold BETWEEN 1 AND 255),parameter_fingerprint BLOB NOT NULL "
|
|
"CHECK(length(parameter_fingerprint)=32));",
|
|
"migrate schema v4 to v5");
|
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
|
const char *forced_failure = getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V5");
|
|
if (result == LARDON3D_PROJECT_DB_OK && forced_failure && strcmp(forced_failure, "1") == 0) {
|
|
result = execute(database, "INSERT INTO missing_test_table VALUES(1)",
|
|
"forced migration v5 failure");
|
|
}
|
|
#endif
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result =
|
|
execute(database, "UPDATE metadata SET value=5 WHERE key='schema_version' AND value=4",
|
|
"finish schema v5 migration");
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && from_version != 0 && from_version < 6) {
|
|
result = execute(database, schema_visual_v6, "migrate schema v5 to v6");
|
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
|
const char *forced_failure = getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V6");
|
|
if (result == LARDON3D_PROJECT_DB_OK && forced_failure && strcmp(forced_failure, "1") == 0) {
|
|
result = execute(database, "INSERT INTO missing_test_table VALUES(1)",
|
|
"forced migration v6 failure");
|
|
}
|
|
#endif
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = execute(database,
|
|
"UPDATE metadata SET value=6 WHERE key='schema_version' AND value=5",
|
|
"finish schema v6 migration");
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && from_version < 7) {
|
|
result = execute(database, schema_precision_v7, "migrate schema v6 to v7");
|
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
|
const char *forced_failure = getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V7");
|
|
if (result == LARDON3D_PROJECT_DB_OK && forced_failure && strcmp(forced_failure, "1") == 0) {
|
|
result = execute(database, "INSERT INTO missing_test_table VALUES(1)",
|
|
"forced migration v7 failure");
|
|
}
|
|
#endif
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = execute(database,
|
|
"UPDATE metadata SET value=7 WHERE key='schema_version' AND value=6",
|
|
"finish schema v7 migration");
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && from_version < 8) {
|
|
result = execute(database, schema_candidate_v8, "migrate schema v7 to v8");
|
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
|
const char *forced_failure = getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V8");
|
|
if (result == LARDON3D_PROJECT_DB_OK && forced_failure && strcmp(forced_failure, "1") == 0) {
|
|
result = execute(database, "INSERT INTO missing_test_table VALUES(1)",
|
|
"forced migration v8 failure");
|
|
}
|
|
#endif
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = execute(database,
|
|
"UPDATE metadata SET value=8 WHERE key='schema_version' AND value=7",
|
|
"finish schema v8 migration");
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && from_version < 9) {
|
|
result = execute(database, schema_candidate_generate_v9, "migrate schema v8 to v9");
|
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
|
const char *forced_failure = getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V9");
|
|
if (result == LARDON3D_PROJECT_DB_OK && forced_failure && strcmp(forced_failure, "1") == 0) {
|
|
result = execute(database, "INSERT INTO missing_test_table VALUES(1)",
|
|
"forced migration v9 failure");
|
|
}
|
|
#endif
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = execute(database,
|
|
"UPDATE metadata SET value=9 WHERE key='schema_version' AND value=8",
|
|
"finish schema v9 migration");
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && from_version < 10) {
|
|
result = execute(database, schema_match_result_v10, "migrate schema v9 to v10");
|
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
|
const char *forced_failure = getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V10");
|
|
if (result == LARDON3D_PROJECT_DB_OK && forced_failure && strcmp(forced_failure, "1") == 0) {
|
|
result = execute(database, "INSERT INTO missing_test_table VALUES(1)",
|
|
"forced migration v10 failure");
|
|
}
|
|
#endif
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = execute(database,
|
|
"UPDATE metadata SET value=10 WHERE key='schema_version' AND value=9",
|
|
"finish schema v10 migration");
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && from_version < 11) {
|
|
result = execute(database, schema_matcher_task_v11, "migrate schema v10 to v11");
|
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
|
const char *forced_failure = getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V11");
|
|
if (result == LARDON3D_PROJECT_DB_OK && forced_failure && strcmp(forced_failure, "1") == 0) {
|
|
result = execute(database, "INSERT INTO missing_test_table VALUES(1)",
|
|
"forced migration v11 failure");
|
|
}
|
|
#endif
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = execute(database,
|
|
"UPDATE metadata SET value=11 WHERE key='schema_version' AND value=10",
|
|
"finish schema v11 migration");
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && from_version < 12) {
|
|
result = execute(database, schema_geometric_verification_v12,
|
|
"migrate schema v11 to v12");
|
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
|
const char *forced_failure = getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V12");
|
|
if (result == LARDON3D_PROJECT_DB_OK && forced_failure && strcmp(forced_failure, "1") == 0) {
|
|
result = execute(database, "INSERT INTO missing_test_table VALUES(1)",
|
|
"forced migration v12 failure");
|
|
}
|
|
#endif
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = execute(database,
|
|
"UPDATE metadata SET value=12 WHERE key='schema_version' AND value=11",
|
|
"finish schema v12 migration");
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && from_version < 13) {
|
|
result = execute(database, schema_geometric_verifier_task_v13,
|
|
"migrate schema v12 to v13");
|
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
|
const char *forced_failure = getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V13");
|
|
if (result == LARDON3D_PROJECT_DB_OK && forced_failure && strcmp(forced_failure, "1") == 0) {
|
|
result = execute(database, "INSERT INTO missing_test_table VALUES(1)",
|
|
"forced migration v13 failure");
|
|
}
|
|
#endif
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = execute(database,
|
|
"UPDATE metadata SET value=13 WHERE key='schema_version' AND value=12",
|
|
"finish schema v13 migration");
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = execute(database, "COMMIT", "commit migration");
|
|
}
|
|
if (result != LARDON3D_PROJECT_DB_OK) {
|
|
(void)execute(database, "ROLLBACK", "rollback migration");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static Lardon3DProjectDbResult read_schema_version(Lardon3DProjectDb *database,
|
|
unsigned int *version) {
|
|
if (!table_exists(database->connection, "metadata")) {
|
|
sqlite3_stmt *statement = NULL;
|
|
int code = sqlite3_prepare_v2(database->connection,
|
|
"SELECT 1 FROM sqlite_master WHERE type='table' LIMIT 1", -1,
|
|
&statement, NULL);
|
|
if (code != SQLITE_OK) {
|
|
return sqlite_result(database, code, "inspect schema");
|
|
}
|
|
code = sqlite3_step(statement);
|
|
bool empty = code == SQLITE_DONE;
|
|
(void)sqlite3_finalize(statement);
|
|
if (!empty) {
|
|
copy_error(database->error, "Base incohérente sans metadata.");
|
|
return LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
*version = 0;
|
|
return LARDON3D_PROJECT_DB_OK;
|
|
}
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result =
|
|
prepare(database, "SELECT value FROM metadata WHERE key='schema_version'", &statement);
|
|
if (result != LARDON3D_PROJECT_DB_OK) {
|
|
return result;
|
|
}
|
|
int code = sqlite3_step(statement);
|
|
if (code != SQLITE_ROW || sqlite3_column_type(statement, 0) != SQLITE_INTEGER) {
|
|
(void)sqlite3_finalize(statement);
|
|
copy_error(database->error, "schema_version absente ou invalide.");
|
|
return LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
sqlite3_int64 value = sqlite3_column_int64(statement, 0);
|
|
code = sqlite3_step(statement);
|
|
(void)sqlite3_finalize(statement);
|
|
if (code != SQLITE_DONE || value < 1 || value > UINT_MAX) {
|
|
return LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
*version = (unsigned int)value;
|
|
return LARDON3D_PROJECT_DB_OK;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_open(const char *path, Lardon3DProjectDb **output,
|
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]) {
|
|
if (output) {
|
|
*output = NULL;
|
|
}
|
|
if (!path || !path[0] || !output ||
|
|
strnlen(path, LARDON3D_PROJECT_DB_PATH_CAPACITY) >= LARDON3D_PROJECT_DB_PATH_CAPACITY) {
|
|
copy_error(error, "Chemin de base invalide.");
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
Lardon3DProjectDb *database = calloc(1, sizeof(*database));
|
|
if (!database) {
|
|
copy_error(error, "Allocation impossible.");
|
|
return LARDON3D_PROJECT_DB_IO_ERROR;
|
|
}
|
|
if (pthread_mutex_init(&database->mutex, NULL) != 0) {
|
|
free(database);
|
|
copy_error(error, "Mutex impossible.");
|
|
return LARDON3D_PROJECT_DB_IO_ERROR;
|
|
}
|
|
int code =
|
|
sqlite3_open_v2(path, &database->connection,
|
|
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX, NULL);
|
|
if (code != SQLITE_OK) {
|
|
copy_error(error, database->connection ? sqlite3_errmsg(database->connection)
|
|
: "Ouverture SQLite impossible.");
|
|
if (database->connection) {
|
|
(void)sqlite3_close(database->connection);
|
|
}
|
|
(void)pthread_mutex_destroy(&database->mutex);
|
|
free(database);
|
|
return code == SQLITE_CANTOPEN ? LARDON3D_PROJECT_DB_IO_ERROR : LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
Lardon3DProjectDbResult result =
|
|
execute(database,
|
|
"PRAGMA foreign_keys=ON;PRAGMA journal_mode=DELETE;PRAGMA synchronous=FULL;PRAGMA "
|
|
"busy_timeout=5000",
|
|
"configure SQLite");
|
|
unsigned int version = 0;
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = read_schema_version(database, &version);
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = migrate(database, version);
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
const char *required[] = {"project",
|
|
"tasks",
|
|
"checkpoints",
|
|
"artifacts",
|
|
"image_import_tasks",
|
|
"scansets",
|
|
"image_assets",
|
|
"images",
|
|
"feature_assets",
|
|
"feature_sets",
|
|
"feature_extract_tasks",
|
|
"sift_extract_tasks",
|
|
"feature_support_sets",
|
|
"feature_support_groups",
|
|
"feature_support_members",
|
|
"candidate_pairs",
|
|
"matcher_tasks",
|
|
"match_results",
|
|
"geometric_verification_results",
|
|
"geometric_verifier_tasks"};
|
|
for (size_t index = 0; index < sizeof(required) / sizeof(required[0]) &&
|
|
result == LARDON3D_PROJECT_DB_OK;
|
|
++index) {
|
|
if (!table_exists(database->connection, required[index])) {
|
|
copy_error(database->error, "Schéma v1 incomplet.");
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
}
|
|
}
|
|
if (result != LARDON3D_PROJECT_DB_OK) {
|
|
copy_error(error, database->error);
|
|
(void)sqlite3_close(database->connection);
|
|
(void)pthread_mutex_destroy(&database->mutex);
|
|
free(database);
|
|
return result;
|
|
}
|
|
database->error[0] = '\0';
|
|
copy_error(error, "");
|
|
*output = database;
|
|
return LARDON3D_PROJECT_DB_OK;
|
|
}
|
|
|
|
void lardon3d_project_db_close(Lardon3DProjectDb *database) {
|
|
if (!database) {
|
|
return;
|
|
}
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
(void)sqlite3_close(database->connection);
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
(void)pthread_mutex_destroy(&database->mutex);
|
|
free(database);
|
|
}
|
|
|
|
bool lardon3d_project_db_last_error(Lardon3DProjectDb *database,
|
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]) {
|
|
if (!database || !error) {
|
|
return false;
|
|
}
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
copy_error(error, database->error);
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return true;
|
|
}
|
|
unsigned int lardon3d_project_db_schema_version(Lardon3DProjectDb *database) {
|
|
return database ? LARDON3D_PROJECT_DB_SCHEMA_VERSION : 0U;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_legacy_catalog_pending(Lardon3DProjectDb *database,
|
|
bool *pending) {
|
|
if (pending) {
|
|
*pending = false;
|
|
}
|
|
if (!database || !pending) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
database, "SELECT value FROM metadata WHERE key='legacy_image_catalog_pending'", &statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
int code = sqlite3_step(statement);
|
|
sqlite3_int64 value = sqlite3_column_int64(statement, 0);
|
|
if (code != SQLITE_ROW || (value != 0 && value != 1) ||
|
|
sqlite3_step(statement) != SQLITE_DONE) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
} else {
|
|
*pending = value == 1;
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
static Lardon3DProjectDbResult step_done(Lardon3DProjectDb *database, sqlite3_stmt *statement,
|
|
const char *context) {
|
|
int code = sqlite3_step(statement);
|
|
if (code != SQLITE_DONE) {
|
|
Lardon3DProjectDbResult result = sqlite_result(database, code, context);
|
|
(void)sqlite3_finalize(statement);
|
|
return result;
|
|
}
|
|
code = sqlite3_finalize(statement);
|
|
return code == SQLITE_OK ? LARDON3D_PROJECT_DB_OK : sqlite_result(database, code, context);
|
|
}
|
|
|
|
static bool copy_column(sqlite3_stmt *statement, int column, char *destination, size_t capacity) {
|
|
if (sqlite3_column_type(statement, column) != SQLITE_TEXT) {
|
|
return false;
|
|
}
|
|
int bytes = sqlite3_column_bytes(statement, column);
|
|
const unsigned char *text = sqlite3_column_text(statement, column);
|
|
if (!text || bytes < 0 || (size_t)bytes >= capacity) {
|
|
return false;
|
|
}
|
|
memcpy(destination, text, (size_t)bytes);
|
|
destination[bytes] = '\0';
|
|
return true;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_set_project(Lardon3DProjectDb *database,
|
|
const Lardon3DProjectDbProject *project) {
|
|
if (!database || !project ||
|
|
!bounded_text(project->stable_id, sizeof(project->stable_id), false) ||
|
|
!bounded_text(project->name, sizeof(project->name), false) || project->created_at < 0 ||
|
|
project->updated_at < project->created_at) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
database,
|
|
"INSERT INTO project(singleton,stable_id,name,created_at,updated_at) VALUES(1,?1,?2,?3,?4) "
|
|
"ON CONFLICT(singleton) DO UPDATE SET name=excluded.name,updated_at=excluded.updated_at "
|
|
"WHERE project.stable_id=excluded.stable_id AND project.created_at=excluded.created_at",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_text(statement, 1, project->stable_id, -1, SQLITE_TRANSIENT);
|
|
(void)sqlite3_bind_text(statement, 2, project->name, -1, SQLITE_TRANSIENT);
|
|
(void)sqlite3_bind_int64(statement, 3, project->created_at);
|
|
(void)sqlite3_bind_int64(statement, 4, project->updated_at);
|
|
result = step_done(database, statement, "set project");
|
|
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1) {
|
|
copy_error(database->error, "Identité de projet contradictoire.");
|
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
}
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_get_project(Lardon3DProjectDb *database,
|
|
Lardon3DProjectDbProject *project) {
|
|
if (!database || !project) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
database, "SELECT stable_id,name,created_at,updated_at FROM project WHERE singleton=1",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
int code = sqlite3_step(statement);
|
|
if (code == SQLITE_DONE) {
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
} else if (code != SQLITE_ROW ||
|
|
!copy_column(statement, 0, project->stable_id, sizeof(project->stable_id)) ||
|
|
!copy_column(statement, 1, project->name, sizeof(project->name))) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
} else {
|
|
project->created_at = sqlite3_column_int64(statement, 2);
|
|
project->updated_at = sqlite3_column_int64(statement, 3);
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
static bool valid_checkpoint(const Lardon3DProjectDbCheckpoint *checkpoint) {
|
|
return checkpoint && bounded_text(checkpoint->path, sizeof(checkpoint->path), false) &&
|
|
checkpoint->format_version > 0 && checkpoint->updated_at >= 0 &&
|
|
checkpoint->durability >= LARDON3D_DB_CHECKPOINT_DURABLE &&
|
|
checkpoint->durability <= LARDON3D_DB_CHECKPOINT_PUBLISHED_NOT_DURABLE;
|
|
}
|
|
|
|
static bool valid_durable_task(const Lardon3DTaskDurableSnapshot *snapshot, int64_t updated_at) {
|
|
return snapshot && valid_task_id(snapshot->id) &&
|
|
bounded_text(snapshot->name, sizeof(snapshot->name), false) &&
|
|
valid_state(snapshot->saved_state) && valid_state(snapshot->recovery_state) &&
|
|
snapshot->recovery_state ==
|
|
((snapshot->saved_state == TASK_RUNNING || snapshot->saved_state == TASK_PAUSED)
|
|
? TASK_PENDING
|
|
: snapshot->saved_state) &&
|
|
snapshot->progress <= 100 && snapshot->sequence_count <= INT_MAX &&
|
|
snapshot->started_at.tv_sec >= 0 && snapshot->started_at.tv_sec <= INT64_MAX &&
|
|
snapshot->started_at.tv_nsec >= 0 && snapshot->started_at.tv_nsec < 1000000000L &&
|
|
snapshot->finished_at.tv_sec >= 0 && snapshot->finished_at.tv_sec <= INT64_MAX &&
|
|
snapshot->finished_at.tv_nsec >= 0 && snapshot->finished_at.tv_nsec < 1000000000L &&
|
|
updated_at >= 0;
|
|
}
|
|
|
|
static Lardon3DProjectDbResult
|
|
record_task_internal(Lardon3DProjectDb *database, const Lardon3DTaskDurableSnapshot *snapshot,
|
|
const char *task_kind, uint32_t task_kind_version,
|
|
const Lardon3DProjectDbCheckpoint *checkpoint, const char *source_path,
|
|
uint64_t scanset_id, const Lardon3DProjectDbFeatureExtractTask *feature,
|
|
const Lardon3DProjectDbSiftExtractTask *sift,
|
|
const Lardon3DProjectDbVisualIndexUpdateTask *visual,
|
|
const Lardon3DProjectDbCandidatePairGenerateTask *candidate_pair,
|
|
const Lardon3DProjectDbMatcherTask *matcher,
|
|
const Lardon3DProjectDbGeometricVerifierTask *geometric_verifier,
|
|
int64_t updated_at) {
|
|
bool typed = task_kind != NULL;
|
|
if (!database || !valid_durable_task(snapshot, updated_at) ||
|
|
(typed && (!lardon3d_task_kind_is_valid(task_kind) || task_kind_version == 0)) ||
|
|
(!typed && task_kind_version != 0) ||
|
|
(source_path && !bounded_text(source_path, LARDON3D_PROJECT_DB_PATH_CAPACITY, false)) ||
|
|
(source_path && !valid_task_id(scanset_id)) ||
|
|
(feature &&
|
|
(!valid_task_id(feature->task_id) || feature->task_id != snapshot->id ||
|
|
!valid_task_id(feature->image_id) ||
|
|
!lardon3d_task_kind_is_valid(feature->extractor_kind) || feature->extractor_version == 0 ||
|
|
feature->max_features == 0 || feature->max_features > 8192 ||
|
|
feature->pyramid_levels == 0 || feature->pyramid_levels > 16 ||
|
|
feature->fast_threshold == 0 || feature->fast_threshold > 255)) ||
|
|
(sift &&
|
|
(!valid_task_id(sift->task_id) || sift->task_id != snapshot->id ||
|
|
!valid_task_id(sift->image_id) ||
|
|
(strcmp(sift->extractor_kind, "sift") != 0 &&
|
|
strcmp(sift->extractor_kind, "rootsift") != 0) ||
|
|
sift->extractor_version != 1 || sift->max_features == 0 ||
|
|
sift->max_features > 8192 || sift->octave_layers == 0 || sift->octave_layers > 8 ||
|
|
sift->contrast_threshold < 0.001 || sift->contrast_threshold > 0.2 ||
|
|
sift->edge_threshold < 1.0 || sift->edge_threshold > 100.0 || sift->sigma < 0.5 ||
|
|
sift->sigma > 3.0 || sift->grid_rows == 0 || sift->grid_rows > 32 ||
|
|
sift->grid_cols == 0 || sift->grid_cols > 32 || sift->max_features_per_cell == 0 ||
|
|
sift->max_features_per_cell > 8192)) ||
|
|
(visual &&
|
|
(!valid_task_id(visual->task_id) || visual->task_id != snapshot->id ||
|
|
!valid_task_id(visual->visual_index_id) || visual->after_feature_set_id > INT64_MAX)) ||
|
|
(candidate_pair &&
|
|
(!valid_task_id(candidate_pair->task_id) || candidate_pair->task_id != snapshot->id ||
|
|
!valid_task_id(candidate_pair->visual_index_id) ||
|
|
candidate_pair->after_feature_set_id > INT64_MAX ||
|
|
candidate_pair->top_k == 0 || candidate_pair->top_k > 256 ||
|
|
candidate_pair->minimum_evidence_count > 1024 ||
|
|
candidate_pair->scanset_filter < 0 || candidate_pair->scanset_filter > 2)) ||
|
|
(matcher &&
|
|
(!valid_task_id(matcher->task_id) || matcher->task_id != snapshot->id ||
|
|
matcher->after_candidate_pair_id > INT64_MAX ||
|
|
!lardon3d_task_kind_is_valid(matcher->feature_extractor_kind) ||
|
|
matcher->feature_extractor_version == 0 || matcher->matcher_kind < 0 ||
|
|
matcher->matcher_kind > 2 || !isfinite(matcher->ratio_threshold) ||
|
|
matcher->ratio_threshold <= 0.0F || matcher->ratio_threshold >= 1.0F)) ||
|
|
(geometric_verifier &&
|
|
(!valid_task_id(geometric_verifier->task_id) ||
|
|
geometric_verifier->task_id != snapshot->id ||
|
|
geometric_verifier->after_match_result_id > INT64_MAX ||
|
|
!isfinite(geometric_verifier->threshold_pixels) ||
|
|
geometric_verifier->threshold_pixels <= 0.0 ||
|
|
!isfinite(geometric_verifier->confidence) ||
|
|
geometric_verifier->confidence <= 0.0 || geometric_verifier->confidence >= 1.0 ||
|
|
geometric_verifier->max_iterations == 0 ||
|
|
geometric_verifier->max_iterations > INT_MAX ||
|
|
geometric_verifier->min_inlier_count == 0 ||
|
|
geometric_verifier->min_inlier_count > 8192 ||
|
|
!isfinite(geometric_verifier->min_inlier_ratio) ||
|
|
geometric_verifier->min_inlier_ratio < 0.0 ||
|
|
geometric_verifier->min_inlier_ratio > 1.0 ||
|
|
geometric_verifier->seed_policy_version == 0 ||
|
|
geometric_verifier->canonicalization_version == 0)) ||
|
|
(checkpoint && !valid_checkpoint(checkpoint))) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
|
const char *forced_busy = getenv("LARDON3D_TEST_PROJECT_DB_BUSY_CHECKPOINT");
|
|
if (forced_busy && checkpoint && strcmp(forced_busy, "1") == 0) {
|
|
copy_error(database->error, "Verrou DB injecté pour test.");
|
|
return LARDON3D_PROJECT_DB_BUSY;
|
|
}
|
|
#endif
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin task record");
|
|
sqlite3_stmt *statement = NULL;
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = prepare(
|
|
database,
|
|
"INSERT INTO "
|
|
"tasks(task_id,name,task_kind,task_kind_version,saved_state,recovery_state,progress,"
|
|
"sequence_count,started_sec,started_nsec,finished_sec,finished_nsec,updated_at)"
|
|
"VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13) ON CONFLICT(task_id) DO UPDATE SET "
|
|
"name=excluded.name,saved_state=excluded.saved_state,"
|
|
"recovery_state=excluded.recovery_state,progress=excluded.progress,sequence_count="
|
|
"excluded.sequence_count,started_sec=excluded.started_sec,"
|
|
"started_nsec=excluded.started_nsec,finished_sec=excluded.finished_sec,finished_nsec="
|
|
"excluded.finished_nsec,updated_at=excluded.updated_at "
|
|
"WHERE (tasks.task_kind IS NULL AND excluded.task_kind IS NULL) OR "
|
|
"(tasks.task_kind=excluded.task_kind AND "
|
|
"tasks.task_kind_version=excluded.task_kind_version)",
|
|
&statement);
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)snapshot->id);
|
|
(void)sqlite3_bind_text(statement, 2, snapshot->name, -1, SQLITE_TRANSIENT);
|
|
if (typed) {
|
|
(void)sqlite3_bind_text(statement, 3, task_kind, -1, SQLITE_TRANSIENT);
|
|
(void)sqlite3_bind_int64(statement, 4, task_kind_version);
|
|
} else {
|
|
(void)sqlite3_bind_null(statement, 3);
|
|
(void)sqlite3_bind_null(statement, 4);
|
|
}
|
|
(void)sqlite3_bind_int(statement, 5, (int)snapshot->saved_state);
|
|
(void)sqlite3_bind_int(statement, 6, (int)snapshot->recovery_state);
|
|
(void)sqlite3_bind_int(statement, 7, (int)snapshot->progress);
|
|
(void)sqlite3_bind_int(statement, 8, (int)snapshot->sequence_count);
|
|
(void)sqlite3_bind_int64(statement, 9, snapshot->started_at.tv_sec);
|
|
(void)sqlite3_bind_int64(statement, 10, snapshot->started_at.tv_nsec);
|
|
(void)sqlite3_bind_int64(statement, 11, snapshot->finished_at.tv_sec);
|
|
(void)sqlite3_bind_int64(statement, 12, snapshot->finished_at.tv_nsec);
|
|
(void)sqlite3_bind_int64(statement, 13, updated_at);
|
|
result = step_done(database, statement, "upsert task");
|
|
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1) {
|
|
copy_error(database->error, "Type métier de tâche immuable.");
|
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && checkpoint) {
|
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
|
const char *forced_failure = getenv("LARDON3D_TEST_PROJECT_DB_FAIL_CHECKPOINT");
|
|
if (forced_failure && strcmp(forced_failure, "1") == 0) {
|
|
result = execute(database, "INSERT INTO missing_test_table VALUES(1)",
|
|
"forced checkpoint failure");
|
|
}
|
|
#endif
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result =
|
|
prepare(database,
|
|
"UPDATE metadata SET value=CASE WHEN ?1=9223372036854775807 THEN 0 ELSE ?1+1 END "
|
|
"WHERE key='next_task_id' AND value>0 AND value<=?1",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)snapshot->id);
|
|
result = step_done(database, statement, "advance recorded task id");
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && source_path) {
|
|
result =
|
|
prepare(database,
|
|
"INSERT INTO image_import_tasks(task_id,source_path,scanset_id) VALUES(?1,?2,?3) "
|
|
"ON CONFLICT(task_id) DO UPDATE SET source_path=excluded.source_path "
|
|
"WHERE image_import_tasks.source_path=excluded.source_path AND "
|
|
"image_import_tasks.scanset_id=excluded.scanset_id",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)snapshot->id);
|
|
(void)sqlite3_bind_text(statement, 2, source_path, -1, SQLITE_TRANSIENT);
|
|
(void)sqlite3_bind_int64(statement, 3, (sqlite3_int64)scanset_id);
|
|
result = step_done(database, statement, "upsert image import");
|
|
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1) {
|
|
copy_error(database->error, "Source d'import immuable.");
|
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
}
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && feature) {
|
|
result = prepare(
|
|
database,
|
|
"INSERT INTO "
|
|
"feature_extract_tasks(task_id,image_id,extractor_kind,extractor_version,max_features,"
|
|
"pyramid_levels,fast_threshold,parameter_fingerprint) VALUES(?1,?2,?3,?4,?5,?6,?7,?8) "
|
|
"ON CONFLICT(task_id) DO UPDATE SET image_id=excluded.image_id WHERE "
|
|
"feature_extract_tasks.image_id=excluded.image_id AND "
|
|
"feature_extract_tasks.extractor_kind=excluded.extractor_kind AND "
|
|
"feature_extract_tasks.extractor_version=excluded.extractor_version AND "
|
|
"feature_extract_tasks.max_features=excluded.max_features AND "
|
|
"feature_extract_tasks.pyramid_levels=excluded.pyramid_levels AND "
|
|
"feature_extract_tasks.fast_threshold=excluded.fast_threshold AND "
|
|
"feature_extract_tasks.parameter_fingerprint=excluded.parameter_fingerprint",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)feature->task_id);
|
|
(void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)feature->image_id);
|
|
(void)sqlite3_bind_text(statement, 3, feature->extractor_kind, -1, SQLITE_TRANSIENT);
|
|
(void)sqlite3_bind_int64(statement, 4, feature->extractor_version);
|
|
(void)sqlite3_bind_int64(statement, 5, feature->max_features);
|
|
(void)sqlite3_bind_int64(statement, 6, feature->pyramid_levels);
|
|
(void)sqlite3_bind_int64(statement, 7, feature->fast_threshold);
|
|
(void)sqlite3_bind_blob(statement, 8, feature->parameter_fingerprint, 32, SQLITE_TRANSIENT);
|
|
result = step_done(database, statement, "upsert feature extract");
|
|
statement = NULL;
|
|
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1) {
|
|
copy_error(database->error, "Paramètres feature immuables.");
|
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
}
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && visual) {
|
|
result = prepare(database,
|
|
"INSERT INTO visual_index_update_tasks(task_id,visual_index_id,"
|
|
"after_feature_set_id) VALUES(?1,?2,?3) ON CONFLICT(task_id) DO UPDATE SET "
|
|
"after_feature_set_id=excluded.after_feature_set_id WHERE "
|
|
"visual_index_update_tasks.visual_index_id=excluded.visual_index_id",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)visual->task_id);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)visual->visual_index_id);
|
|
sqlite3_bind_int64(statement, 3, (sqlite3_int64)visual->after_feature_set_id);
|
|
result = step_done(database, statement, "upsert visual index update");
|
|
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1) {
|
|
copy_error(database->error, "Index cible de tâche immuable.");
|
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
}
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && candidate_pair) {
|
|
result = prepare(database,
|
|
"INSERT INTO candidate_pair_generate_tasks(task_id,visual_index_id,"
|
|
"after_feature_set_id,top_k,minimum_evidence_count,scanset_filter,"
|
|
"exclude_same_asset) VALUES(?1,?2,?3,?4,?5,?6,?7) ON CONFLICT(task_id) DO "
|
|
"UPDATE SET after_feature_set_id=excluded.after_feature_set_id WHERE "
|
|
"candidate_pair_generate_tasks.visual_index_id=excluded.visual_index_id",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)candidate_pair->task_id);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)candidate_pair->visual_index_id);
|
|
sqlite3_bind_int64(statement, 3, (sqlite3_int64)candidate_pair->after_feature_set_id);
|
|
sqlite3_bind_int64(statement, 4, (sqlite3_int64)candidate_pair->top_k);
|
|
sqlite3_bind_int64(statement, 5, (sqlite3_int64)candidate_pair->minimum_evidence_count);
|
|
sqlite3_bind_int64(statement, 6, (sqlite3_int64)candidate_pair->scanset_filter);
|
|
sqlite3_bind_int64(statement, 7, candidate_pair->exclude_same_asset ? 1 : 0);
|
|
result = step_done(database, statement, "upsert candidate pair generate");
|
|
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1) {
|
|
copy_error(database->error, "Index cible de tâche immuable.");
|
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
}
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && matcher) {
|
|
result = prepare(
|
|
database,
|
|
"INSERT INTO matcher_tasks(task_id,after_candidate_pair_id,feature_extractor_kind,"
|
|
"feature_extractor_version,feature_parameter_fingerprint,matcher_kind,ratio_threshold) "
|
|
"VALUES(?1,?2,?3,?4,?5,?6,?7) ON CONFLICT(task_id) DO UPDATE SET "
|
|
"after_candidate_pair_id=excluded.after_candidate_pair_id WHERE "
|
|
"matcher_tasks.feature_extractor_kind=excluded.feature_extractor_kind AND "
|
|
"matcher_tasks.feature_extractor_version=excluded.feature_extractor_version AND "
|
|
"matcher_tasks.feature_parameter_fingerprint=excluded.feature_parameter_fingerprint AND "
|
|
"matcher_tasks.matcher_kind=excluded.matcher_kind AND "
|
|
"matcher_tasks.ratio_threshold=excluded.ratio_threshold",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)matcher->task_id);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)matcher->after_candidate_pair_id);
|
|
sqlite3_bind_text(statement, 3, matcher->feature_extractor_kind, -1, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int64(statement, 4, matcher->feature_extractor_version);
|
|
sqlite3_bind_blob(statement, 5, matcher->feature_parameter_fingerprint, 32,
|
|
SQLITE_TRANSIENT);
|
|
sqlite3_bind_int(statement, 6, matcher->matcher_kind);
|
|
sqlite3_bind_double(statement, 7, matcher->ratio_threshold);
|
|
result = step_done(database, statement, "upsert matcher task");
|
|
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1) {
|
|
copy_error(database->error, "Configuration Matcher de tâche immuable.");
|
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
}
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && geometric_verifier) {
|
|
result = prepare(
|
|
database,
|
|
"INSERT INTO geometric_verifier_tasks(task_id,after_match_result_id,threshold_pixels,"
|
|
"confidence,max_iterations,min_inlier_count,min_inlier_ratio,seed_policy_version,"
|
|
"canonicalization_version,parameter_fingerprint) VALUES(?1,?2,?3,?4,?5,?6,?7,?8,"
|
|
"?9,?10) ON CONFLICT(task_id) DO UPDATE SET after_match_result_id="
|
|
"excluded.after_match_result_id WHERE geometric_verifier_tasks.threshold_pixels="
|
|
"excluded.threshold_pixels AND geometric_verifier_tasks.confidence=excluded.confidence "
|
|
"AND geometric_verifier_tasks.max_iterations=excluded.max_iterations AND "
|
|
"geometric_verifier_tasks.min_inlier_count=excluded.min_inlier_count AND "
|
|
"geometric_verifier_tasks.min_inlier_ratio=excluded.min_inlier_ratio AND "
|
|
"geometric_verifier_tasks.seed_policy_version=excluded.seed_policy_version AND "
|
|
"geometric_verifier_tasks.canonicalization_version=excluded.canonicalization_version "
|
|
"AND geometric_verifier_tasks.parameter_fingerprint=excluded.parameter_fingerprint",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)geometric_verifier->task_id);
|
|
sqlite3_bind_int64(statement, 2,
|
|
(sqlite3_int64)geometric_verifier->after_match_result_id);
|
|
sqlite3_bind_double(statement, 3, geometric_verifier->threshold_pixels);
|
|
sqlite3_bind_double(statement, 4, geometric_verifier->confidence);
|
|
sqlite3_bind_int64(statement, 5, geometric_verifier->max_iterations);
|
|
sqlite3_bind_int64(statement, 6, geometric_verifier->min_inlier_count);
|
|
sqlite3_bind_double(statement, 7, geometric_verifier->min_inlier_ratio);
|
|
sqlite3_bind_int64(statement, 8, geometric_verifier->seed_policy_version);
|
|
sqlite3_bind_int64(statement, 9, geometric_verifier->canonicalization_version);
|
|
sqlite3_bind_blob(statement, 10, geometric_verifier->parameter_fingerprint, 32,
|
|
SQLITE_TRANSIENT);
|
|
result = step_done(database, statement, "upsert geometric verifier task");
|
|
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1) {
|
|
copy_error(database->error, "Configuration Geometric Verifier de tâche immuable.");
|
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
}
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && sift) {
|
|
result = prepare(
|
|
database,
|
|
"INSERT INTO sift_extract_tasks(task_id,image_id,extractor_kind,extractor_version,"
|
|
"max_features,octave_layers,contrast_threshold,edge_threshold,sigma,grid_rows,grid_cols,"
|
|
"max_features_per_cell,parameter_fingerprint) VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,"
|
|
"?11,?12,?13) ON CONFLICT(task_id) DO UPDATE SET image_id=excluded.image_id WHERE "
|
|
"sift_extract_tasks.image_id=excluded.image_id AND sift_extract_tasks.extractor_kind="
|
|
"excluded.extractor_kind AND sift_extract_tasks.extractor_version=excluded.extractor_version "
|
|
"AND sift_extract_tasks.max_features=excluded.max_features AND sift_extract_tasks."
|
|
"octave_layers=excluded.octave_layers AND sift_extract_tasks.contrast_threshold="
|
|
"excluded.contrast_threshold AND sift_extract_tasks.edge_threshold=excluded.edge_threshold "
|
|
"AND sift_extract_tasks.sigma=excluded.sigma AND sift_extract_tasks.grid_rows="
|
|
"excluded.grid_rows AND sift_extract_tasks.grid_cols=excluded.grid_cols AND "
|
|
"sift_extract_tasks.max_features_per_cell=excluded.max_features_per_cell AND "
|
|
"sift_extract_tasks.parameter_fingerprint=excluded.parameter_fingerprint",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)sift->task_id);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)sift->image_id);
|
|
sqlite3_bind_text(statement, 3, sift->extractor_kind, -1, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int64(statement, 4, sift->extractor_version);
|
|
sqlite3_bind_int64(statement, 5, sift->max_features);
|
|
sqlite3_bind_int64(statement, 6, sift->octave_layers);
|
|
sqlite3_bind_double(statement, 7, sift->contrast_threshold);
|
|
sqlite3_bind_double(statement, 8, sift->edge_threshold);
|
|
sqlite3_bind_double(statement, 9, sift->sigma);
|
|
sqlite3_bind_int64(statement, 10, sift->grid_rows);
|
|
sqlite3_bind_int64(statement, 11, sift->grid_cols);
|
|
sqlite3_bind_int64(statement, 12, sift->max_features_per_cell);
|
|
sqlite3_bind_blob(statement, 13, sift->parameter_fingerprint, 32, SQLITE_TRANSIENT);
|
|
result = step_done(database, statement, "upsert sift extract");
|
|
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1) {
|
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
}
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && checkpoint) {
|
|
result =
|
|
prepare(database,
|
|
"INSERT INTO checkpoints(task_id,path,format_version,durability,updated_at) "
|
|
"VALUES(?1,?2,?3,?4,?5) "
|
|
"ON CONFLICT(task_id) DO UPDATE SET "
|
|
"path=excluded.path,format_version=excluded.format_version,durability=excluded."
|
|
"durability,updated_at=excluded.updated_at",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)snapshot->id);
|
|
(void)sqlite3_bind_text(statement, 2, checkpoint->path, -1, SQLITE_TRANSIENT);
|
|
(void)sqlite3_bind_int64(statement, 3, checkpoint->format_version);
|
|
(void)sqlite3_bind_int(statement, 4, (int)checkpoint->durability);
|
|
(void)sqlite3_bind_int64(statement, 5, checkpoint->updated_at);
|
|
result = step_done(database, statement, "upsert checkpoint");
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = execute(database, "COMMIT", "commit task record");
|
|
}
|
|
if (result != LARDON3D_PROJECT_DB_OK) {
|
|
(void)execute(database, "ROLLBACK", "rollback task record");
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_record_task(
|
|
Lardon3DProjectDb *database, const Lardon3DTaskDurableSnapshot *snapshot, const char *task_kind,
|
|
uint32_t task_kind_version, const Lardon3DProjectDbCheckpoint *checkpoint, int64_t updated_at) {
|
|
return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint, NULL, 0,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, updated_at);
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_record_image_import_task(
|
|
Lardon3DProjectDb *database, const Lardon3DTaskDurableSnapshot *snapshot, const char *task_kind,
|
|
uint32_t task_kind_version, const Lardon3DProjectDbCheckpoint *checkpoint,
|
|
const char *source_path, uint64_t scanset_id, int64_t updated_at) {
|
|
if (!source_path) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint,
|
|
source_path, scanset_id, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
updated_at);
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_record_feature_extract_task(
|
|
Lardon3DProjectDb *database, const Lardon3DTaskDurableSnapshot *snapshot, const char *task_kind,
|
|
uint32_t task_kind_version, const Lardon3DProjectDbCheckpoint *checkpoint,
|
|
const Lardon3DProjectDbFeatureExtractTask *parameters, int64_t updated_at) {
|
|
if (!parameters) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint, NULL, 0,
|
|
parameters, NULL, NULL, NULL, NULL, NULL, updated_at);
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_record_sift_extract_task(
|
|
Lardon3DProjectDb *database, const Lardon3DTaskDurableSnapshot *snapshot,
|
|
const char *task_kind, uint32_t task_kind_version,
|
|
const Lardon3DProjectDbCheckpoint *checkpoint,
|
|
const Lardon3DProjectDbSiftExtractTask *parameters, int64_t updated_at) {
|
|
if (!parameters) return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint, NULL, 0,
|
|
NULL, parameters, NULL, NULL, NULL, NULL, updated_at);
|
|
}
|
|
|
|
static bool read_task(sqlite3_stmt *statement, Lardon3DProjectDbTask *task) {
|
|
memset(task, 0, sizeof(*task));
|
|
sqlite3_int64 id = sqlite3_column_int64(statement, 0);
|
|
bool has_kind = sqlite3_column_type(statement, 2) != SQLITE_NULL;
|
|
bool has_kind_version = sqlite3_column_type(statement, 3) != SQLITE_NULL;
|
|
int progress = sqlite3_column_int(statement, 6);
|
|
sqlite3_int64 sequence_count = sqlite3_column_int64(statement, 7);
|
|
sqlite3_int64 started_nsec = sqlite3_column_int64(statement, 9);
|
|
sqlite3_int64 finished_nsec = sqlite3_column_int64(statement, 11);
|
|
if (id <= 0 || !copy_column(statement, 1, task->name, sizeof(task->name))) {
|
|
return false;
|
|
}
|
|
if (has_kind != has_kind_version) {
|
|
return false;
|
|
}
|
|
if (has_kind) {
|
|
sqlite3_int64 version = sqlite3_column_int64(statement, 3);
|
|
if (!copy_column(statement, 2, task->task_kind, sizeof(task->task_kind)) ||
|
|
!lardon3d_task_kind_is_valid(task->task_kind) || version <= 0 || version > UINT32_MAX) {
|
|
return false;
|
|
}
|
|
task->has_task_kind = true;
|
|
task->task_kind_version = (uint32_t)version;
|
|
}
|
|
task->task_id = (uint64_t)id;
|
|
task->saved_state = (Lardon3DTaskState)sqlite3_column_int(statement, 4);
|
|
task->recovery_state = (Lardon3DTaskState)sqlite3_column_int(statement, 5);
|
|
if (progress < 0 || progress > 100 || sequence_count < 0 || sequence_count > UINT_MAX ||
|
|
started_nsec < 0 || started_nsec >= 1000000000 || finished_nsec < 0 ||
|
|
finished_nsec >= 1000000000 ||
|
|
!database_time(sqlite3_column_int64(statement, 8), &task->started_at.tv_sec) ||
|
|
!database_time(sqlite3_column_int64(statement, 10), &task->finished_at.tv_sec)) {
|
|
return false;
|
|
}
|
|
task->progress = (unsigned int)progress;
|
|
task->sequence_count = (unsigned int)sequence_count;
|
|
task->started_at.tv_nsec = (long)started_nsec;
|
|
task->finished_at.tv_nsec = (long)finished_nsec;
|
|
task->updated_at = sqlite3_column_int64(statement, 12);
|
|
if (sqlite3_column_type(statement, 13) != SQLITE_NULL) {
|
|
task->has_checkpoint = true;
|
|
if (!copy_column(statement, 13, task->checkpoint.path, sizeof(task->checkpoint.path))) {
|
|
return false;
|
|
}
|
|
sqlite3_int64 format_version = sqlite3_column_int64(statement, 14);
|
|
if (format_version <= 0 || format_version > UINT32_MAX) {
|
|
return false;
|
|
}
|
|
task->checkpoint.format_version = (uint32_t)format_version;
|
|
task->checkpoint.durability =
|
|
(Lardon3DProjectDbCheckpointDurability)sqlite3_column_int(statement, 15);
|
|
task->checkpoint.updated_at = sqlite3_column_int64(statement, 16);
|
|
if (task->checkpoint.durability < LARDON3D_DB_CHECKPOINT_DURABLE ||
|
|
task->checkpoint.durability > LARDON3D_DB_CHECKPOINT_PUBLISHED_NOT_DURABLE) {
|
|
return false;
|
|
}
|
|
}
|
|
return valid_state(task->saved_state) && valid_state(task->recovery_state) &&
|
|
task->progress <= 100;
|
|
}
|
|
|
|
static const char task_select[] =
|
|
"SELECT "
|
|
"t.task_id,t.name,t.task_kind,t.task_kind_version,t.saved_state,t.recovery_state,t.progress,t."
|
|
"sequence_count,t.started_sec,t.started_nsec,"
|
|
"t.finished_sec,t.finished_nsec,t.updated_at,c.path,c.format_version,c.durability,c.updated_at "
|
|
"FROM tasks t LEFT JOIN checkpoints c ON c.task_id=t.task_id ";
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_load_task(Lardon3DProjectDb *database, uint64_t task_id,
|
|
Lardon3DProjectDbTask *task) {
|
|
if (!database || !valid_task_id(task_id) || !task) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
char sql[768];
|
|
(void)snprintf(sql, sizeof(sql), "%s WHERE t.task_id=?1", task_select);
|
|
Lardon3DProjectDbResult result = prepare(database, sql, &statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)task_id);
|
|
int code = sqlite3_step(statement);
|
|
if (code == SQLITE_DONE) {
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
} else if (code != SQLITE_ROW || !read_task(statement, task)) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_list_recoverable(Lardon3DProjectDb *database,
|
|
uint64_t after_task_id,
|
|
Lardon3DProjectDbTask *tasks,
|
|
size_t capacity, size_t *count) {
|
|
if (count) {
|
|
*count = 0;
|
|
}
|
|
if (!database || !tasks || !count || after_task_id > INT64_MAX || capacity == 0 ||
|
|
capacity > LARDON3D_PROJECT_DB_RECOVERY_PAGE_MAX) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
|
const char *forced_busy = getenv("LARDON3D_TEST_PROJECT_DB_BUSY_RECOVERY");
|
|
if (forced_busy && strcmp(forced_busy, "1") == 0) {
|
|
return LARDON3D_PROJECT_DB_BUSY;
|
|
}
|
|
#endif
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
char sql[896];
|
|
(void)snprintf(sql, sizeof(sql),
|
|
"%s WHERE t.recovery_state=?1 AND c.task_id IS NOT NULL AND t.task_id>?2 ORDER "
|
|
"BY t.task_id LIMIT ?3",
|
|
task_select);
|
|
Lardon3DProjectDbResult result = prepare(database, sql, &statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int(statement, 1, TASK_PENDING);
|
|
(void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)after_task_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_task(statement, &tasks[*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 recoverable");
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_allocate_task_id(Lardon3DProjectDb *database,
|
|
uint64_t *task_id) {
|
|
if (task_id) {
|
|
*task_id = 0;
|
|
}
|
|
if (!database || !task_id) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin task id allocation");
|
|
sqlite3_stmt *statement = NULL;
|
|
sqlite3_int64 next = 0;
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = prepare(database, "SELECT value FROM metadata WHERE key='next_task_id'", &statement);
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
int code = sqlite3_step(statement);
|
|
if (code != SQLITE_ROW || sqlite3_column_type(statement, 0) != SQLITE_INTEGER) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
} else {
|
|
next = sqlite3_column_int64(statement, 0);
|
|
if (next <= 0 || next > INT64_MAX) {
|
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
}
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
statement = NULL;
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = prepare(database,
|
|
"UPDATE metadata SET value=CASE WHEN value=9223372036854775807 THEN 0 "
|
|
"ELSE value+1 END WHERE key='next_task_id' AND value=?1",
|
|
&statement);
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, next);
|
|
result = step_done(database, statement, "advance task id");
|
|
statement = NULL;
|
|
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1) {
|
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = execute(database, "COMMIT", "commit task id allocation");
|
|
}
|
|
if (result != LARDON3D_PROJECT_DB_OK) {
|
|
(void)execute(database, "ROLLBACK", "rollback task id allocation");
|
|
} else {
|
|
*task_id = (uint64_t)next;
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult
|
|
lardon3d_project_db_load_image_import(Lardon3DProjectDb *database, uint64_t task_id,
|
|
Lardon3DProjectDbImageImport *parameters) {
|
|
if (!database || !valid_task_id(task_id) || !parameters) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
memset(parameters, 0, sizeof(*parameters));
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result =
|
|
prepare(database, "SELECT source_path,scanset_id FROM image_import_tasks WHERE task_id=?1",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)task_id);
|
|
int code = sqlite3_step(statement);
|
|
if (code == SQLITE_DONE) {
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
} else if (code != SQLITE_ROW || !copy_column(statement, 0, parameters->source_path,
|
|
sizeof(parameters->source_path))) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
} else {
|
|
sqlite3_int64 scanset = sqlite3_column_int64(statement, 1);
|
|
if (scanset <= 0) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
} else {
|
|
parameters->task_id = task_id;
|
|
parameters->scanset_id = (uint64_t)scanset;
|
|
}
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
static bool valid_catalog_id(uint64_t value) { return value > 0 && value <= INT64_MAX; }
|
|
|
|
static bool valid_original_name(const char *name) {
|
|
return bounded_text(name, LARDON3D_PROJECT_DB_IMAGE_NAME_CAPACITY, false) && !strchr(name, '/') &&
|
|
!strchr(name, '\\') && !strchr(name, '\t') && !strchr(name, '\r') && !strchr(name, '\n');
|
|
}
|
|
|
|
static bool valid_relative_asset_path(const char *path) {
|
|
return bounded_text(path, LARDON3D_PROJECT_DB_PATH_CAPACITY, false) && path[0] != '/' &&
|
|
!strchr(path, '\\') && strncmp(path, "../", 3) != 0 && !strstr(path, "/../") &&
|
|
strcmp(path, "..") != 0;
|
|
}
|
|
|
|
static bool canonical_asset_path(const unsigned char hash[LARDON3D_PROJECT_DB_SHA256_SIZE],
|
|
const char *path) {
|
|
static const char digits[] = "0123456789abcdef";
|
|
char hex[65];
|
|
for (size_t index = 0; index < LARDON3D_PROJECT_DB_SHA256_SIZE; ++index) {
|
|
hex[index * 2] = digits[hash[index] >> 4];
|
|
hex[index * 2 + 1] = digits[hash[index] & 15U];
|
|
}
|
|
hex[64] = '\0';
|
|
char expected[LARDON3D_PROJECT_DB_PATH_CAPACITY];
|
|
int written = snprintf(expected, sizeof(expected), "assets/images/%c%c/%s", hex[0], hex[1], hex);
|
|
return written > 0 && (size_t)written < sizeof(expected) && strcmp(path, expected) == 0;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_create_scanset(Lardon3DProjectDb *database,
|
|
const char *name,
|
|
Lardon3DProjectDbScanSet *scanset) {
|
|
if (!database || !scanset ||
|
|
!bounded_text(name, LARDON3D_PROJECT_DB_SCANSET_NAME_CAPACITY, false)) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
int64_t now = (int64_t)time(NULL);
|
|
if (now < 0) {
|
|
return LARDON3D_PROJECT_DB_IO_ERROR;
|
|
}
|
|
memset(scanset, 0, sizeof(*scanset));
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
database, "INSERT INTO scansets(name,created_at,updated_at) VALUES(?1,?2,?2)", &statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_text(statement, 1, name, -1, SQLITE_TRANSIENT);
|
|
(void)sqlite3_bind_int64(statement, 2, now);
|
|
result = step_done(database, statement, "create scanset");
|
|
}
|
|
sqlite3_int64 id = sqlite3_last_insert_rowid(database->connection);
|
|
if (result == LARDON3D_PROJECT_DB_OK && id <= 0) {
|
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
scanset->scanset_id = (uint64_t)id;
|
|
scanset->created_at = now;
|
|
scanset->updated_at = now;
|
|
(void)snprintf(scanset->name, sizeof(scanset->name), "%s", name);
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
static bool read_scanset(sqlite3_stmt *statement, Lardon3DProjectDbScanSet *scanset) {
|
|
sqlite3_int64 id = sqlite3_column_int64(statement, 0);
|
|
sqlite3_int64 created = sqlite3_column_int64(statement, 2);
|
|
sqlite3_int64 updated = sqlite3_column_int64(statement, 3);
|
|
if (id <= 0 || created < 0 || updated < created ||
|
|
!copy_column(statement, 1, scanset->name, sizeof(scanset->name))) {
|
|
return false;
|
|
}
|
|
scanset->scanset_id = (uint64_t)id;
|
|
scanset->created_at = created;
|
|
scanset->updated_at = updated;
|
|
return true;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_load_scanset(Lardon3DProjectDb *database,
|
|
uint64_t scanset_id,
|
|
Lardon3DProjectDbScanSet *scanset) {
|
|
if (!database || !valid_catalog_id(scanset_id) || !scanset) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
memset(scanset, 0, sizeof(*scanset));
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
database, "SELECT scanset_id,name,created_at,updated_at FROM scansets WHERE scanset_id=?1",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)scanset_id);
|
|
int code = sqlite3_step(statement);
|
|
if (code == SQLITE_DONE) {
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
} else if (code != SQLITE_ROW || !read_scanset(statement, scanset)) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_list_scansets(Lardon3DProjectDb *database,
|
|
uint64_t after_scanset_id,
|
|
Lardon3DProjectDbScanSet *scansets,
|
|
size_t capacity, size_t *count) {
|
|
if (count) {
|
|
*count = 0;
|
|
}
|
|
if (!database || !scansets || !count || after_scanset_id > INT64_MAX || 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 scanset_id,name,created_at,updated_at FROM scansets WHERE scanset_id>?1 "
|
|
"ORDER BY scanset_id LIMIT ?2",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)after_scanset_id);
|
|
(void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)capacity);
|
|
int code = SQLITE_DONE;
|
|
while (*count < capacity && (code = sqlite3_step(statement)) == SQLITE_ROW) {
|
|
if (!read_scanset(statement, &scansets[*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 scansets");
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
static bool read_candidate_pair(sqlite3_stmt *statement, Lardon3DProjectDbCandidatePair *pair) {
|
|
sqlite3_int64 id = sqlite3_column_int64(statement, 0);
|
|
sqlite3_int64 image_a = sqlite3_column_int64(statement, 1);
|
|
sqlite3_int64 image_b = sqlite3_column_int64(statement, 2);
|
|
sqlite3_int64 created = sqlite3_column_int64(statement, 3);
|
|
if (id <= 0 || image_a <= 0 || image_b <= 0 || image_a >= image_b || created < 0) {
|
|
return false;
|
|
}
|
|
pair->candidate_pair_id = (uint64_t)id;
|
|
pair->image_id_a = (uint64_t)image_a;
|
|
pair->image_id_b = (uint64_t)image_b;
|
|
pair->created_at = created;
|
|
return true;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_create_candidate_pair(
|
|
Lardon3DProjectDb *database, uint64_t image_id_a, uint64_t image_id_b, int64_t created_at,
|
|
Lardon3DProjectDbCandidatePair *pair) {
|
|
if (!database || !valid_catalog_id(image_id_a) || !valid_catalog_id(image_id_b) ||
|
|
created_at < 0 || !pair) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
uint64_t canonical_a = image_id_a < image_id_b ? image_id_a : image_id_b;
|
|
uint64_t canonical_b = image_id_a < image_id_b ? image_id_b : image_id_a;
|
|
memset(pair, 0, sizeof(*pair));
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
database,
|
|
"INSERT INTO candidate_pairs(image_id_a,image_id_b,created_at) VALUES(?1,?2,?3)",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)canonical_a);
|
|
(void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)canonical_b);
|
|
(void)sqlite3_bind_int64(statement, 3, created_at);
|
|
result = step_done(database, statement, "create candidate pair");
|
|
}
|
|
sqlite3_int64 id = sqlite3_last_insert_rowid(database->connection);
|
|
if (result == LARDON3D_PROJECT_DB_OK && id <= 0) {
|
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
pair->candidate_pair_id = (uint64_t)id;
|
|
pair->image_id_a = canonical_a;
|
|
pair->image_id_b = canonical_b;
|
|
pair->created_at = created_at;
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_load_candidate_pair(
|
|
Lardon3DProjectDb *database, uint64_t candidate_pair_id,
|
|
Lardon3DProjectDbCandidatePair *pair) {
|
|
if (!database || !valid_catalog_id(candidate_pair_id) || !pair) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
memset(pair, 0, sizeof(*pair));
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
database,
|
|
"SELECT candidate_pair_id,image_id_a,image_id_b,created_at FROM candidate_pairs WHERE "
|
|
"candidate_pair_id=?1",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)candidate_pair_id);
|
|
int code = sqlite3_step(statement);
|
|
if (code == SQLITE_DONE) {
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
} else if (code != SQLITE_ROW || !read_candidate_pair(statement, pair)) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_find_candidate_pair(
|
|
Lardon3DProjectDb *database, uint64_t image_id_a, uint64_t image_id_b,
|
|
Lardon3DProjectDbCandidatePair *pair) {
|
|
if (!database || !valid_catalog_id(image_id_a) || !valid_catalog_id(image_id_b) || !pair) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
uint64_t canonical_a = image_id_a < image_id_b ? image_id_a : image_id_b;
|
|
uint64_t canonical_b = image_id_a < image_id_b ? image_id_b : image_id_a;
|
|
memset(pair, 0, sizeof(*pair));
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
database,
|
|
"SELECT candidate_pair_id,image_id_a,image_id_b,created_at FROM candidate_pairs WHERE "
|
|
"image_id_a=?1 AND image_id_b=?2",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)canonical_a);
|
|
(void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)canonical_b);
|
|
int code = sqlite3_step(statement);
|
|
if (code == SQLITE_DONE) {
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
} else if (code != SQLITE_ROW || !read_candidate_pair(statement, pair)) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_list_candidate_pairs(
|
|
Lardon3DProjectDb *database, uint64_t after_candidate_pair_id,
|
|
Lardon3DProjectDbCandidatePair *pairs, size_t capacity, size_t *count) {
|
|
if (count) {
|
|
*count = 0;
|
|
}
|
|
if (!database || !pairs || !count || after_candidate_pair_id > INT64_MAX || capacity == 0 ||
|
|
capacity > LARDON3D_PROJECT_DB_CANDIDATE_PAIR_PAGE_MAX) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
database,
|
|
"SELECT candidate_pair_id,image_id_a,image_id_b,created_at FROM candidate_pairs WHERE "
|
|
"candidate_pair_id>?1 ORDER BY candidate_pair_id LIMIT ?2",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)after_candidate_pair_id);
|
|
(void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)capacity);
|
|
int code = SQLITE_DONE;
|
|
while (*count < capacity && (code = sqlite3_step(statement)) == SQLITE_ROW) {
|
|
if (!read_candidate_pair(statement, &pairs[*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 candidate pairs");
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
static bool read_image(sqlite3_stmt *statement, Lardon3DProjectDbImage *image,
|
|
Lardon3DProjectDbImageAsset *asset) {
|
|
sqlite3_int64 image_id = sqlite3_column_int64(statement, 0);
|
|
sqlite3_int64 scanset_id = sqlite3_column_int64(statement, 1);
|
|
sqlite3_int64 asset_id = sqlite3_column_int64(statement, 2);
|
|
sqlite3_int64 size = sqlite3_column_int64(statement, 7);
|
|
int hash_size = sqlite3_column_bytes(statement, 5);
|
|
const void *hash = sqlite3_column_blob(statement, 5);
|
|
if (image_id <= 0 || scanset_id <= 0 || asset_id <= 0 || size < 0 ||
|
|
hash_size != LARDON3D_PROJECT_DB_SHA256_SIZE || !hash ||
|
|
!copy_column(statement, 3, image->original_name, sizeof(image->original_name)) ||
|
|
!copy_column(statement, 4, image->source_path, sizeof(image->source_path)) ||
|
|
!copy_column(statement, 6, asset->path, sizeof(asset->path)) ||
|
|
!valid_original_name(image->original_name) || !valid_relative_asset_path(asset->path)) {
|
|
return false;
|
|
}
|
|
image->image_id = (uint64_t)image_id;
|
|
image->scanset_id = (uint64_t)scanset_id;
|
|
image->asset_id = (uint64_t)asset_id;
|
|
asset->asset_id = (uint64_t)asset_id;
|
|
memcpy(asset->sha256, hash, LARDON3D_PROJECT_DB_SHA256_SIZE);
|
|
if (!canonical_asset_path(asset->sha256, asset->path)) {
|
|
return false;
|
|
}
|
|
asset->size_bytes = (uint64_t)size;
|
|
asset->state = (Lardon3DProjectDbImageAssetState)sqlite3_column_int(statement, 8);
|
|
asset->created_at = sqlite3_column_int64(statement, 9);
|
|
image->has_producer_task = sqlite3_column_type(statement, 10) != SQLITE_NULL;
|
|
sqlite3_int64 producer = sqlite3_column_int64(statement, 10);
|
|
if (image->has_producer_task && producer <= 0) {
|
|
return false;
|
|
}
|
|
image->producer_task_id = image->has_producer_task ? (uint64_t)producer : 0;
|
|
image->imported_at = sqlite3_column_int64(statement, 11);
|
|
return asset->state == LARDON3D_DB_IMAGE_ASSET_READY && asset->created_at >= 0 &&
|
|
image->imported_at >= 0;
|
|
}
|
|
|
|
static const char image_select[] =
|
|
"SELECT "
|
|
"i.image_id,i.scanset_id,i.asset_id,i.original_name,i.source_path,a.sha256,a.path,a.size_bytes,"
|
|
"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 ";
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_register_image(
|
|
Lardon3DProjectDb *database, uint64_t scanset_id,
|
|
const unsigned char sha256[LARDON3D_PROJECT_DB_SHA256_SIZE], const char *asset_path,
|
|
uint64_t size_bytes, const char *original_name, const char *source_path,
|
|
uint64_t producer_task_id, int64_t imported_at, Lardon3DProjectDbImageRegisterStatus *status,
|
|
Lardon3DProjectDbImage *image) {
|
|
if (!database || !valid_catalog_id(scanset_id) || !sha256 ||
|
|
!valid_relative_asset_path(asset_path) || !canonical_asset_path(sha256, asset_path) ||
|
|
size_bytes > INT64_MAX || !valid_original_name(original_name) ||
|
|
!bounded_text(source_path, LARDON3D_PROJECT_DB_PATH_CAPACITY, false) ||
|
|
(producer_task_id != 0 && !valid_task_id(producer_task_id)) || imported_at < 0 || !status ||
|
|
!image) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
|
const char *failure = getenv("LARDON3D_TEST_PROJECT_DB_FAIL_IMAGE_REGISTER");
|
|
if (failure && strcmp(failure, "1") == 0) {
|
|
return LARDON3D_PROJECT_DB_BUSY;
|
|
}
|
|
#endif
|
|
memset(image, 0, sizeof(*image));
|
|
*status = LARDON3D_PROJECT_DB_IMAGE_REGISTERED;
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin image 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, imported_at);
|
|
result = step_done(database, statement, "insert image asset");
|
|
statement = NULL;
|
|
}
|
|
sqlite3_int64 asset_id = 0;
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result =
|
|
prepare(database, "SELECT asset_id,path,size_bytes,state 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);
|
|
char stored_path[LARDON3D_PROJECT_DB_PATH_CAPACITY];
|
|
if (code != SQLITE_ROW || !copy_column(statement, 1, stored_path, sizeof(stored_path)) ||
|
|
strcmp(stored_path, asset_path) != 0 ||
|
|
sqlite3_column_int64(statement, 2) != (sqlite3_int64)size_bytes ||
|
|
sqlite3_column_int(statement, 3) != LARDON3D_DB_IMAGE_ASSET_READY) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
} else {
|
|
asset_id = sqlite3_column_int64(statement, 0);
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
statement = NULL;
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = prepare(
|
|
database,
|
|
"INSERT INTO "
|
|
"images(scanset_id,asset_id,original_name,source_path,producer_task_id,imported_at) "
|
|
"VALUES(?1,?2,?3,?4,?5,?6) ON CONFLICT(scanset_id,asset_id) DO NOTHING",
|
|
&statement);
|
|
}
|
|
int inserted = 0;
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)scanset_id);
|
|
(void)sqlite3_bind_int64(statement, 2, asset_id);
|
|
(void)sqlite3_bind_text(statement, 3, original_name, -1, SQLITE_TRANSIENT);
|
|
(void)sqlite3_bind_text(statement, 4, source_path, -1, SQLITE_TRANSIENT);
|
|
if (producer_task_id) {
|
|
(void)sqlite3_bind_int64(statement, 5, (sqlite3_int64)producer_task_id);
|
|
} else {
|
|
(void)sqlite3_bind_null(statement, 5);
|
|
}
|
|
(void)sqlite3_bind_int64(statement, 6, imported_at);
|
|
result = step_done(database, statement, "insert logical image");
|
|
statement = NULL;
|
|
inserted = sqlite3_changes(database->connection);
|
|
}
|
|
sqlite3_int64 image_id = 0;
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = prepare(database, "SELECT image_id FROM images WHERE scanset_id=?1 AND asset_id=?2",
|
|
&statement);
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)scanset_id);
|
|
(void)sqlite3_bind_int64(statement, 2, asset_id);
|
|
int code = sqlite3_step(statement);
|
|
if (code != SQLITE_ROW || (image_id = sqlite3_column_int64(statement, 0)) <= 0) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
statement = NULL;
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = execute(database, "COMMIT", "commit image register");
|
|
}
|
|
if (result != LARDON3D_PROJECT_DB_OK) {
|
|
(void)execute(database, "ROLLBACK", "rollback image register");
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
if (result != LARDON3D_PROJECT_DB_OK) {
|
|
return result;
|
|
}
|
|
*status = inserted == 1 ? LARDON3D_PROJECT_DB_IMAGE_REGISTERED
|
|
: LARDON3D_PROJECT_DB_IMAGE_ALREADY_PRESENT;
|
|
return lardon3d_project_db_load_image(database, (uint64_t)image_id, image,
|
|
&(Lardon3DProjectDbImageAsset){0});
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_load_image(Lardon3DProjectDb *database,
|
|
uint64_t image_id,
|
|
Lardon3DProjectDbImage *image,
|
|
Lardon3DProjectDbImageAsset *asset) {
|
|
if (!database || !valid_catalog_id(image_id) || !image || !asset) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
memset(image, 0, sizeof(*image));
|
|
memset(asset, 0, sizeof(*asset));
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
char sql[768];
|
|
(void)snprintf(sql, sizeof(sql), "%s WHERE i.image_id=?1", image_select);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(database, sql, &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_image(statement, image, asset)) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_list_images(Lardon3DProjectDb *database,
|
|
uint64_t scanset_id,
|
|
uint64_t after_image_id,
|
|
Lardon3DProjectDbImage *images,
|
|
Lardon3DProjectDbImageAsset *assets,
|
|
size_t capacity, size_t *count) {
|
|
if (count) {
|
|
*count = 0;
|
|
}
|
|
if (!database || !valid_catalog_id(scanset_id) || after_image_id > INT64_MAX || !images ||
|
|
!assets || !count || capacity == 0 || capacity > LARDON3D_PROJECT_DB_CATALOG_PAGE_MAX) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
Lardon3DProjectDbScanSet scanset;
|
|
Lardon3DProjectDbResult exists = lardon3d_project_db_load_scanset(database, scanset_id, &scanset);
|
|
if (exists != LARDON3D_PROJECT_DB_OK) {
|
|
return exists;
|
|
}
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
char sql[896];
|
|
(void)snprintf(sql, sizeof(sql),
|
|
"%s WHERE i.scanset_id=?1 AND i.image_id>?2 ORDER BY i.image_id LIMIT ?3",
|
|
image_select);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(database, sql, &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_image_id);
|
|
(void)sqlite3_bind_int64(statement, 3, (sqlite3_int64)capacity);
|
|
int code = SQLITE_DONE;
|
|
while (*count < capacity && (code = sqlite3_step(statement)) == SQLITE_ROW) {
|
|
memset(&images[*count], 0, sizeof(images[*count]));
|
|
memset(&assets[*count], 0, sizeof(assets[*count]));
|
|
if (!read_image(statement, &images[*count], &assets[*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 images");
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_count_images(Lardon3DProjectDb *database,
|
|
uint64_t scanset_id, uint64_t *count) {
|
|
if (count) {
|
|
*count = 0;
|
|
}
|
|
if (!database || !valid_catalog_id(scanset_id) || !count) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
Lardon3DProjectDbScanSet scanset;
|
|
Lardon3DProjectDbResult exists = lardon3d_project_db_load_scanset(database, scanset_id, &scanset);
|
|
if (exists != LARDON3D_PROJECT_DB_OK) {
|
|
return exists;
|
|
}
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result =
|
|
prepare(database, "SELECT count(*) FROM images WHERE scanset_id=?1", &statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)scanset_id);
|
|
int code = sqlite3_step(statement);
|
|
sqlite3_int64 value = sqlite3_column_int64(statement, 0);
|
|
if (code != SQLITE_ROW || value < 0) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
} else {
|
|
*count = (uint64_t)value;
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult
|
|
lardon3d_project_db_create_artifact(Lardon3DProjectDb *database,
|
|
const Lardon3DProjectDbArtifact *artifact) {
|
|
if (!database || !artifact ||
|
|
!bounded_text(artifact->artifact_id, sizeof(artifact->artifact_id), false) ||
|
|
!bounded_text(artifact->kind, sizeof(artifact->kind), false) ||
|
|
!bounded_text(artifact->path, sizeof(artifact->path), false) ||
|
|
artifact->state != LARDON3D_DB_ARTIFACT_STAGED || artifact->size_bytes != 0 ||
|
|
artifact->created_at < 0 || artifact->updated_at < artifact->created_at ||
|
|
(artifact->has_producer_task && !valid_task_id(artifact->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 "
|
|
"artifacts(artifact_id,kind,path,state,size_bytes,producer_task_id,created_at,"
|
|
"updated_at) VALUES(?1,?2,?3,0,0,?4,?5,?6)",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_text(statement, 1, artifact->artifact_id, -1, SQLITE_TRANSIENT);
|
|
(void)sqlite3_bind_text(statement, 2, artifact->kind, -1, SQLITE_TRANSIENT);
|
|
(void)sqlite3_bind_text(statement, 3, artifact->path, -1, SQLITE_TRANSIENT);
|
|
if (artifact->has_producer_task) {
|
|
(void)sqlite3_bind_int64(statement, 4, (sqlite3_int64)artifact->producer_task_id);
|
|
} else {
|
|
(void)sqlite3_bind_null(statement, 4);
|
|
}
|
|
(void)sqlite3_bind_int64(statement, 5, artifact->created_at);
|
|
(void)sqlite3_bind_int64(statement, 6, artifact->updated_at);
|
|
result = step_done(database, statement, "create artifact");
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_mark_artifact_ready(Lardon3DProjectDb *database,
|
|
const char *artifact_id,
|
|
int64_t updated_at) {
|
|
if (!database || !bounded_text(artifact_id, LARDON3D_PROJECT_DB_ID_CAPACITY, false) ||
|
|
updated_at < 0) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
char path[LARDON3D_PROJECT_DB_PATH_CAPACITY];
|
|
Lardon3DProjectDbArtifact artifact;
|
|
Lardon3DProjectDbResult result =
|
|
lardon3d_project_db_load_artifact(database, artifact_id, &artifact);
|
|
if (result != LARDON3D_PROJECT_DB_OK) {
|
|
return result;
|
|
}
|
|
(void)snprintf(path, sizeof(path), "%s", artifact.path);
|
|
struct stat information;
|
|
if (stat(path, &information) != 0 || !S_ISREG(information.st_mode) || information.st_size < 0) {
|
|
return LARDON3D_PROJECT_DB_IO_ERROR;
|
|
}
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
result = prepare(
|
|
database,
|
|
"UPDATE artifacts SET state=1,size_bytes=?1,updated_at=?2 WHERE artifact_id=?3 AND path=?4",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, information.st_size);
|
|
(void)sqlite3_bind_int64(statement, 2, updated_at);
|
|
(void)sqlite3_bind_text(statement, 3, artifact_id, -1, SQLITE_TRANSIENT);
|
|
(void)sqlite3_bind_text(statement, 4, path, -1, SQLITE_TRANSIENT);
|
|
result = step_done(database, statement, "mark artifact ready");
|
|
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_load_artifact(Lardon3DProjectDb *database,
|
|
const char *artifact_id,
|
|
Lardon3DProjectDbArtifact *artifact) {
|
|
if (!database || !artifact ||
|
|
!bounded_text(artifact_id, LARDON3D_PROJECT_DB_ID_CAPACITY, false)) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
database,
|
|
"SELECT artifact_id,kind,path,state,size_bytes,producer_task_id,created_at,updated_at FROM "
|
|
"artifacts WHERE artifact_id=?1",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_text(statement, 1, artifact_id, -1, SQLITE_TRANSIENT);
|
|
int code = sqlite3_step(statement);
|
|
if (code == SQLITE_DONE) {
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
} else if (code != SQLITE_ROW ||
|
|
!copy_column(statement, 0, artifact->artifact_id, sizeof(artifact->artifact_id)) ||
|
|
!copy_column(statement, 1, artifact->kind, sizeof(artifact->kind)) ||
|
|
!copy_column(statement, 2, artifact->path, sizeof(artifact->path))) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
} else {
|
|
sqlite3_int64 size = sqlite3_column_int64(statement, 4);
|
|
sqlite3_int64 producer = sqlite3_column_int64(statement, 5);
|
|
artifact->state = (Lardon3DProjectDbArtifactState)sqlite3_column_int(statement, 3);
|
|
artifact->size_bytes = size >= 0 ? (uint64_t)size : 0;
|
|
artifact->has_producer_task = sqlite3_column_type(statement, 5) != SQLITE_NULL;
|
|
artifact->producer_task_id =
|
|
artifact->has_producer_task && producer > 0 ? (uint64_t)producer : 0;
|
|
artifact->created_at = sqlite3_column_int64(statement, 6);
|
|
artifact->updated_at = sqlite3_column_int64(statement, 7);
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK &&
|
|
(artifact->state < LARDON3D_DB_ARTIFACT_STAGED ||
|
|
artifact->state > LARDON3D_DB_ARTIFACT_READY ||
|
|
(artifact->has_producer_task && artifact->producer_task_id == 0))) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
static bool canonical_feature_path(const unsigned char hash[32], const char *path) {
|
|
static const char digits[] = "0123456789abcdef";
|
|
char hex[65];
|
|
for (size_t i = 0; i < 32; ++i) {
|
|
hex[2 * i] = digits[hash[i] >> 4];
|
|
hex[2 * i + 1] = digits[hash[i] & 15];
|
|
}
|
|
hex[64] = '\0';
|
|
char expected[LARDON3D_PROJECT_DB_PATH_CAPACITY];
|
|
int n = snprintf(expected, sizeof(expected), "assets/features/%c%c/%s", hex[0], hex[1], hex);
|
|
return n > 0 && (size_t)n < sizeof(expected) && strcmp(expected, path) == 0;
|
|
}
|
|
|
|
static bool read_feature_set(sqlite3_stmt *s, Lardon3DProjectDbFeatureSet *set) {
|
|
memset(set, 0, sizeof(*set));
|
|
sqlite3_int64 id = sqlite3_column_int64(s, 0), image = sqlite3_column_int64(s, 1),
|
|
asset = sqlite3_column_int64(s, 2);
|
|
sqlite3_int64 version = sqlite3_column_int64(s, 4), count = sqlite3_column_int64(s, 7),
|
|
type = sqlite3_column_int64(s, 8), dim = sqlite3_column_int64(s, 9);
|
|
const void *fp = sqlite3_column_blob(s, 5), *source = sqlite3_column_blob(s, 6),
|
|
*hash = sqlite3_column_blob(s, 12);
|
|
sqlite3_int64 size = sqlite3_column_int64(s, 14), durability = sqlite3_column_int64(s, 15),
|
|
producer = sqlite3_column_int64(s, 10);
|
|
if (id <= 0 || image <= 0 || asset <= 0 || version <= 0 || version > UINT32_MAX || count < 0 ||
|
|
count > 8192 || type < 1 || type > 2 || dim <= 0 || dim > 4096 ||
|
|
sqlite3_column_bytes(s, 5) != 32 || sqlite3_column_bytes(s, 6) != 32 ||
|
|
sqlite3_column_bytes(s, 12) != 32 || !fp || !source || !hash || size < 0 || durability < 0 ||
|
|
durability > 1 || !copy_column(s, 3, set->extractor_kind, sizeof(set->extractor_kind)) ||
|
|
!lardon3d_task_kind_is_valid(set->extractor_kind) ||
|
|
!copy_column(s, 13, set->asset.path, sizeof(set->asset.path))) {
|
|
return false;
|
|
}
|
|
set->feature_set_id = (uint64_t)id;
|
|
set->image_id = (uint64_t)image;
|
|
set->feature_asset_id = (uint64_t)asset;
|
|
set->extractor_version = (uint32_t)version;
|
|
memcpy(set->parameter_fingerprint, fp, 32);
|
|
memcpy(set->source_image_sha256, source, 32);
|
|
set->feature_count = (uint32_t)count;
|
|
set->descriptor_type = (uint32_t)type;
|
|
set->descriptor_dimension = (uint32_t)dim;
|
|
set->has_producer_task = sqlite3_column_type(s, 10) != SQLITE_NULL;
|
|
if (set->has_producer_task && producer <= 0) {
|
|
return false;
|
|
}
|
|
set->producer_task_id = set->has_producer_task ? (uint64_t)producer : 0;
|
|
set->created_at = sqlite3_column_int64(s, 11);
|
|
set->asset.feature_asset_id = (uint64_t)asset;
|
|
memcpy(set->asset.sha256, hash, 32);
|
|
set->asset.size_bytes = (uint64_t)size;
|
|
set->asset.durability = (Lardon3DProjectDbFeatureDurability)durability;
|
|
set->asset.created_at = sqlite3_column_int64(s, 16);
|
|
sqlite3_int64 occupied = sqlite3_column_int64(s, 17);
|
|
sqlite3_int64 total_cells = sqlite3_column_int64(s, 18);
|
|
set->coverage_ratio = sqlite3_column_double(s, 19);
|
|
set->feature_density_per_megapixel = sqlite3_column_double(s, 20);
|
|
if (occupied < 0 || occupied > 1024 || total_cells < occupied || total_cells > 1024 ||
|
|
!isfinite(set->coverage_ratio) || set->coverage_ratio < 0.0 ||
|
|
set->coverage_ratio > 1.0 || !isfinite(set->feature_density_per_megapixel) ||
|
|
set->feature_density_per_megapixel < 0.0) {
|
|
return false;
|
|
}
|
|
set->occupied_cells = (uint32_t)occupied;
|
|
set->total_cells = (uint32_t)total_cells;
|
|
return set->created_at >= 0 && set->asset.created_at >= 0 &&
|
|
canonical_feature_path(set->asset.sha256, set->asset.path);
|
|
}
|
|
|
|
static const char feature_select[] =
|
|
"SELECT "
|
|
"f.feature_set_id,f.image_id,f.feature_asset_id,f.extractor_kind,f.extractor_version,f."
|
|
"parameter_fingerprint,f.source_image_sha256,f.feature_count,f.descriptor_type,f.descriptor_"
|
|
"dimension,f.producer_task_id,f.created_at,a.sha256,a.path,a.size_bytes,a.durability,a.created_"
|
|
"at,f.occupied_cells,f.total_cells,f.coverage_ratio,f.feature_density_per_megapixel FROM "
|
|
"feature_sets f JOIN feature_assets a ON a.feature_asset_id=f.feature_asset_id ";
|
|
|
|
static Lardon3DProjectDbResult verify_feature_source_locked(Lardon3DProjectDb *db,
|
|
uint64_t image_id,
|
|
const unsigned char source[32]) {
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(db,
|
|
"SELECT a.sha256 FROM images i JOIN image_assets a ON "
|
|
"a.asset_id=i.asset_id WHERE i.image_id=?1",
|
|
&statement);
|
|
if (result != LARDON3D_PROJECT_DB_OK) {
|
|
return result;
|
|
}
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)image_id);
|
|
int code = sqlite3_step(statement);
|
|
if (code != SQLITE_ROW || sqlite3_column_bytes(statement, 0) != 32 ||
|
|
memcmp(sqlite3_column_blob(statement, 0), source, 32) != 0) {
|
|
result = code == SQLITE_DONE ? LARDON3D_PROJECT_DB_NOT_FOUND : LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
}
|
|
sqlite3_finalize(statement);
|
|
return result;
|
|
}
|
|
|
|
static Lardon3DProjectDbResult find_or_insert_feature_asset_locked(
|
|
Lardon3DProjectDb *db, const unsigned char hash[32], const char *path, uint64_t size,
|
|
Lardon3DProjectDbFeatureDurability durability, int64_t created, sqlite3_int64 *asset_id) {
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result =
|
|
prepare(db,
|
|
"INSERT INTO feature_assets(sha256,path,size_bytes,durability,created_at) "
|
|
"VALUES(?1,?2,?3,?4,?5) ON CONFLICT(sha256) DO NOTHING",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_blob(statement, 1, hash, 32, SQLITE_TRANSIENT);
|
|
sqlite3_bind_text(statement, 2, path, -1, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int64(statement, 3, (sqlite3_int64)size);
|
|
sqlite3_bind_int(statement, 4, (int)durability);
|
|
sqlite3_bind_int64(statement, 5, created);
|
|
result = step_done(db, statement, "insert feature asset");
|
|
statement = NULL;
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = prepare(db,
|
|
"SELECT feature_asset_id,path,size_bytes,durability "
|
|
"FROM feature_assets WHERE sha256=?1",
|
|
&statement);
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_blob(statement, 1, hash, 32, SQLITE_TRANSIENT);
|
|
int code = sqlite3_step(statement);
|
|
char stored[4096];
|
|
sqlite3_int64 stored_durability = sqlite3_column_int64(statement, 3);
|
|
if (code != SQLITE_ROW || !copy_column(statement, 1, stored, sizeof(stored)) ||
|
|
strcmp(stored, path) != 0 || sqlite3_column_int64(statement, 2) != (sqlite3_int64)size ||
|
|
stored_durability < 0 || stored_durability > 1) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
} else {
|
|
*asset_id = sqlite3_column_int64(statement, 0);
|
|
}
|
|
sqlite3_finalize(statement);
|
|
statement = NULL;
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && durability == LARDON3D_DB_FEATURE_ASSET_DURABLE) {
|
|
result = prepare(db,
|
|
"UPDATE feature_assets SET durability=?1 "
|
|
"WHERE feature_asset_id=?2 AND durability=?3",
|
|
&statement);
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && durability == LARDON3D_DB_FEATURE_ASSET_DURABLE) {
|
|
sqlite3_bind_int(statement, 1, LARDON3D_DB_FEATURE_ASSET_DURABLE);
|
|
sqlite3_bind_int64(statement, 2, *asset_id);
|
|
sqlite3_bind_int(statement, 3, LARDON3D_DB_FEATURE_ASSET_PUBLISHED_NOT_DURABLE);
|
|
result = step_done(db, statement, "promote feature asset durability");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static Lardon3DProjectDbResult
|
|
insert_feature_set_locked(Lardon3DProjectDb *db, uint64_t image_id, sqlite3_int64 asset_id,
|
|
const char *kind, uint32_t version, const unsigned char fp[32],
|
|
const unsigned char source[32], uint32_t count, uint32_t type,
|
|
uint32_t dim, uint32_t occupied, uint32_t total_cells, double coverage,
|
|
double density, uint64_t producer, int64_t created) {
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result =
|
|
prepare(db,
|
|
"INSERT INTO feature_sets(image_id,feature_asset_id,extractor_kind,extractor_version,"
|
|
"parameter_fingerprint,source_image_sha256,feature_count,descriptor_type,"
|
|
"descriptor_dimension,producer_task_id,created_at,occupied_cells,total_cells,"
|
|
"coverage_ratio,feature_density_per_megapixel) "
|
|
"VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15) ON CONFLICT("
|
|
"image_id,extractor_kind,"
|
|
"extractor_version,parameter_fingerprint) DO NOTHING",
|
|
&statement);
|
|
if (result != LARDON3D_PROJECT_DB_OK) {
|
|
return result;
|
|
}
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)image_id);
|
|
sqlite3_bind_int64(statement, 2, asset_id);
|
|
sqlite3_bind_text(statement, 3, kind, -1, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int64(statement, 4, version);
|
|
sqlite3_bind_blob(statement, 5, fp, 32, SQLITE_TRANSIENT);
|
|
sqlite3_bind_blob(statement, 6, source, 32, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int64(statement, 7, count);
|
|
sqlite3_bind_int64(statement, 8, type);
|
|
sqlite3_bind_int64(statement, 9, dim);
|
|
if (producer) {
|
|
sqlite3_bind_int64(statement, 10, (sqlite3_int64)producer);
|
|
} else {
|
|
sqlite3_bind_null(statement, 10);
|
|
}
|
|
sqlite3_bind_int64(statement, 11, created);
|
|
sqlite3_bind_int64(statement, 12, occupied);
|
|
sqlite3_bind_int64(statement, 13, total_cells);
|
|
sqlite3_bind_double(statement, 14, coverage);
|
|
sqlite3_bind_double(statement, 15, density);
|
|
return step_done(db, statement, "insert feature set");
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_register_feature_set_quality(
|
|
Lardon3DProjectDb *db, uint64_t image_id, const char *kind, uint32_t version,
|
|
const unsigned char fp[32], const unsigned char source[32], uint32_t count, uint32_t type,
|
|
uint32_t dim, uint32_t occupied, uint32_t total_cells, double coverage, double density,
|
|
const unsigned char hash[32], const char *path, uint64_t size,
|
|
Lardon3DProjectDbFeatureDurability durability, uint64_t producer, int64_t created,
|
|
Lardon3DProjectDbFeatureSet *out) {
|
|
if (!db || !valid_catalog_id(image_id) || !kind || !lardon3d_task_kind_is_valid(kind) ||
|
|
version == 0 || !fp || !source || count > 8192 || type < 1 || type > 2 || dim == 0 ||
|
|
dim > 4096 || occupied > total_cells || total_cells > 1024 || !isfinite(coverage) ||
|
|
coverage < 0.0 || coverage > 1.0 || !isfinite(density) || density < 0.0 || !hash ||
|
|
!canonical_feature_path(hash, path) ||
|
|
size > INT64_MAX ||
|
|
durability < 0 || durability > 1 || (producer && !valid_task_id(producer)) || created < 0 ||
|
|
!out) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
|
const char *failure = getenv("LARDON3D_TEST_PROJECT_DB_FAIL_FEATURE_REGISTER");
|
|
if (failure && strcmp(failure, "1") == 0) {
|
|
return LARDON3D_PROJECT_DB_BUSY;
|
|
}
|
|
#endif
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
Lardon3DProjectDbResult result = execute(db, "BEGIN IMMEDIATE", "begin feature register");
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = verify_feature_source_locked(db, image_id, source);
|
|
}
|
|
sqlite3_int64 asset_id = 0;
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result =
|
|
find_or_insert_feature_asset_locked(db, hash, path, size, durability, created, &asset_id);
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = insert_feature_set_locked(db, image_id, asset_id, kind, version, fp, source, count,
|
|
type, dim, occupied, total_cells, coverage, density, producer,
|
|
created);
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = execute(db, "COMMIT", "commit feature register");
|
|
}
|
|
if (result != LARDON3D_PROJECT_DB_OK) {
|
|
(void)execute(db, "ROLLBACK", "rollback feature register");
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result == LARDON3D_PROJECT_DB_OK
|
|
? lardon3d_project_db_find_feature_set(db, image_id, kind, version, fp, out)
|
|
: result;
|
|
}
|
|
|
|
static bool read_support_set(sqlite3_stmt *s, Lardon3DProjectDbFeatureSupportSet *out) {
|
|
sqlite3_int64 id = sqlite3_column_int64(s, 0);
|
|
sqlite3_int64 image = sqlite3_column_int64(s, 1);
|
|
sqlite3_int64 first = sqlite3_column_int64(s, 2);
|
|
sqlite3_int64 second = sqlite3_column_int64(s, 3);
|
|
const void *fp = sqlite3_column_blob(s, 5);
|
|
sqlite3_int64 created = sqlite3_column_int64(s, 6);
|
|
sqlite3_int64 groups = sqlite3_column_int64(s, 7);
|
|
double radius = sqlite3_column_double(s, 4);
|
|
if (id <= 0 || image <= 0 || first <= 0 || second <= 0 || first == second || !fp ||
|
|
sqlite3_column_bytes(s, 5) != 32 || !isfinite(radius) || radius <= 0.0 || radius > 64.0 ||
|
|
created < 0 || groups < 0 || groups > 16384) {
|
|
return false;
|
|
}
|
|
memset(out, 0, sizeof(*out));
|
|
out->feature_support_set_id = (uint64_t)id;
|
|
out->image_id = (uint64_t)image;
|
|
out->first_feature_set_id = (uint64_t)first;
|
|
out->second_feature_set_id = (uint64_t)second;
|
|
out->radius_pixels = radius;
|
|
memcpy(out->parameter_fingerprint, fp, 32);
|
|
out->created_at = created;
|
|
out->group_count = (uint32_t)groups;
|
|
return true;
|
|
}
|
|
|
|
static Lardon3DProjectDbResult validate_support_sources_locked(
|
|
Lardon3DProjectDb *db, const Lardon3DProjectDbFeatureSupportSet *config,
|
|
const Lardon3DProjectDbFeatureSupportGroup *groups, size_t count) {
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
db, "SELECT image_id,feature_count,extractor_kind FROM feature_sets WHERE "
|
|
"feature_set_id IN(?1,?2) ORDER BY feature_set_id=?2",
|
|
&statement);
|
|
if (result != LARDON3D_PROJECT_DB_OK) return result;
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)config->first_feature_set_id);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)config->second_feature_set_id);
|
|
uint32_t limits[2] = {0};
|
|
for (unsigned source = 0; source < 2; ++source) {
|
|
int step = sqlite3_step(statement);
|
|
const unsigned char *kind = step == SQLITE_ROW ? sqlite3_column_text(statement, 2) : NULL;
|
|
sqlite3_int64 image_id = step == SQLITE_ROW ? sqlite3_column_int64(statement, 0) : 0;
|
|
sqlite3_int64 feature_count = step == SQLITE_ROW ? sqlite3_column_int64(statement, 1) : -1;
|
|
bool expected_kind = source == 0 ? kind && strcmp((const char *)kind, "orb") == 0
|
|
: kind && (strcmp((const char *)kind, "sift") == 0 ||
|
|
strcmp((const char *)kind, "rootsift") == 0);
|
|
if (step != SQLITE_ROW || image_id != (sqlite3_int64)config->image_id || feature_count < 0 ||
|
|
feature_count > 8192 || !expected_kind) {
|
|
sqlite3_finalize(statement);
|
|
return LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
}
|
|
limits[source] = (uint32_t)feature_count;
|
|
}
|
|
sqlite3_finalize(statement);
|
|
for (size_t i = 0; i < count; ++i) {
|
|
unsigned first_source = groups[i].first_member_from_second_set ? 1U : 0U;
|
|
if (groups[i].first_feature_index >= limits[first_source] ||
|
|
(groups[i].has_second_feature && groups[i].second_feature_index >= limits[1])) {
|
|
return LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
}
|
|
}
|
|
return LARDON3D_PROJECT_DB_OK;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_load_feature_support(
|
|
Lardon3DProjectDb *db, uint64_t image_id, uint64_t first_id, uint64_t second_id,
|
|
const unsigned char fp[32], Lardon3DProjectDbFeatureSupportSet *out) {
|
|
if (!db || !valid_catalog_id(image_id) || !valid_catalog_id(first_id) ||
|
|
!valid_catalog_id(second_id) || first_id == second_id || !fp || !out) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
sqlite3_stmt *s = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
db, "SELECT s.feature_support_set_id,s.image_id,s.first_feature_set_id,"
|
|
"s.second_feature_set_id,s.radius_pixels,s.parameter_fingerprint,s.created_at,"
|
|
"COUNT(g.feature_support_group_id) FROM feature_support_sets s LEFT JOIN "
|
|
"feature_support_groups g ON g.feature_support_set_id=s.feature_support_set_id WHERE "
|
|
"s.image_id=?1 AND s.first_feature_set_id=?2 AND s.second_feature_set_id=?3 AND "
|
|
"s.parameter_fingerprint=?4 GROUP BY s.feature_support_set_id",
|
|
&s);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(s, 1, (sqlite3_int64)image_id);
|
|
sqlite3_bind_int64(s, 2, (sqlite3_int64)first_id);
|
|
sqlite3_bind_int64(s, 3, (sqlite3_int64)second_id);
|
|
sqlite3_bind_blob(s, 4, fp, 32, SQLITE_TRANSIENT);
|
|
int step = sqlite3_step(s);
|
|
if (step == SQLITE_ROW) result = read_support_set(s, out) ? LARDON3D_PROJECT_DB_OK
|
|
: LARDON3D_PROJECT_DB_CORRUPT;
|
|
else result = step == SQLITE_DONE ? LARDON3D_PROJECT_DB_NOT_FOUND
|
|
: sqlite_result(db, step, "load feature support");
|
|
sqlite3_finalize(s);
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_list_feature_support_groups(
|
|
Lardon3DProjectDb *db, uint64_t support_set_id, uint64_t after_group_id,
|
|
Lardon3DProjectDbFeatureSupportGroup *groups, size_t capacity, size_t *count) {
|
|
if (count) *count = 0;
|
|
if (!db || !valid_catalog_id(support_set_id) || after_group_id > INT64_MAX || !groups ||
|
|
!count || capacity == 0 || capacity > 256) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
db, "SELECT g.feature_support_group_id,g.feature_support_set_id,g.x,g.y,g.distance_pixels,"
|
|
"g.support_count,first_member.feature_index,second_member.feature_index FROM "
|
|
"feature_support_groups g "
|
|
"JOIN feature_support_sets s ON s.feature_support_set_id=g.feature_support_set_id "
|
|
"LEFT JOIN feature_support_members first_member ON first_member."
|
|
"feature_support_group_id=g.feature_support_group_id AND first_member.feature_set_id="
|
|
"s.first_feature_set_id LEFT JOIN feature_support_members second_member ON "
|
|
"second_member.feature_support_group_id=g.feature_support_group_id AND second_member."
|
|
"feature_set_id=s.second_feature_set_id WHERE g.feature_support_set_id=?1 AND "
|
|
"g.feature_support_group_id>?2 ORDER BY g.feature_support_group_id LIMIT ?3",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)support_set_id);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)after_group_id);
|
|
sqlite3_bind_int64(statement, 3, (sqlite3_int64)capacity);
|
|
while (*count < capacity) {
|
|
int step = sqlite3_step(statement);
|
|
if (step == SQLITE_DONE) break;
|
|
bool has_first = sqlite3_column_type(statement, 6) != SQLITE_NULL;
|
|
bool has_second = sqlite3_column_type(statement, 7) != SQLITE_NULL;
|
|
sqlite3_int64 id = sqlite3_column_int64(statement, 0);
|
|
sqlite3_int64 set_id = sqlite3_column_int64(statement, 1);
|
|
sqlite3_int64 support_count = sqlite3_column_int64(statement, 5);
|
|
sqlite3_int64 first_index = has_first ? sqlite3_column_int64(statement, 6) : 0;
|
|
sqlite3_int64 second_index = has_second ? sqlite3_column_int64(statement, 7) : 0;
|
|
double x = sqlite3_column_double(statement, 2), y = sqlite3_column_double(statement, 3);
|
|
double distance = sqlite3_column_double(statement, 4);
|
|
if (step != SQLITE_ROW || id <= 0 || set_id != (sqlite3_int64)support_set_id ||
|
|
!isfinite(x) || !isfinite(y) || !isfinite(distance) || distance < 0.0 ||
|
|
distance > 64.0 || support_count < 1 || support_count > 2 ||
|
|
has_first + has_second != support_count || first_index < 0 || first_index >= 8192 ||
|
|
second_index < 0 || second_index >= 8192) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
break;
|
|
}
|
|
Lardon3DProjectDbFeatureSupportGroup *group = &groups[(*count)++];
|
|
memset(group, 0, sizeof(*group));
|
|
group->feature_support_group_id = (uint64_t)id;
|
|
group->feature_support_set_id = support_set_id;
|
|
group->x = x;
|
|
group->y = y;
|
|
group->distance_pixels = distance;
|
|
group->support_count = (uint32_t)support_count;
|
|
group->first_member_from_second_set = !has_first;
|
|
group->first_feature_index = (uint32_t)(has_first ? first_index : second_index);
|
|
group->has_second_feature = has_first && has_second;
|
|
group->second_feature_index = (uint32_t)second_index;
|
|
}
|
|
sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_publish_feature_support(
|
|
Lardon3DProjectDb *db, const Lardon3DProjectDbFeatureSupportSet *config,
|
|
const Lardon3DProjectDbFeatureSupportGroup *groups, size_t count,
|
|
Lardon3DProjectDbFeatureSupportSet *out) {
|
|
if (!db || !config || !out || !valid_catalog_id(config->image_id) ||
|
|
!valid_catalog_id(config->first_feature_set_id) ||
|
|
!valid_catalog_id(config->second_feature_set_id) ||
|
|
config->first_feature_set_id == config->second_feature_set_id ||
|
|
!isfinite(config->radius_pixels) || config->radius_pixels <= 0.0 ||
|
|
config->radius_pixels > 64.0 || count > 16384 || (count && !groups) ||
|
|
config->created_at < 0) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
for (size_t i = 0; i < count; ++i) {
|
|
if (!isfinite(groups[i].x) || !isfinite(groups[i].y) ||
|
|
!isfinite(groups[i].distance_pixels) || groups[i].distance_pixels < 0.0 ||
|
|
groups[i].distance_pixels > config->radius_pixels || groups[i].support_count < 1 ||
|
|
groups[i].support_count > 2 || groups[i].first_feature_index >= 8192 ||
|
|
(groups[i].support_count == 2 && groups[i].first_member_from_second_set) ||
|
|
groups[i].has_second_feature != (groups[i].support_count == 2) ||
|
|
(groups[i].has_second_feature && groups[i].second_feature_index >= 8192)) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
}
|
|
Lardon3DProjectDbFeatureSupportSet existing;
|
|
Lardon3DProjectDbResult found = lardon3d_project_db_load_feature_support(
|
|
db, config->image_id, config->first_feature_set_id, config->second_feature_set_id,
|
|
config->parameter_fingerprint, &existing);
|
|
if (found == LARDON3D_PROJECT_DB_OK) {
|
|
if (existing.radius_pixels != config->radius_pixels)
|
|
return LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
*out = existing;
|
|
return LARDON3D_PROJECT_DB_OK;
|
|
}
|
|
if (found != LARDON3D_PROJECT_DB_NOT_FOUND) return found;
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
Lardon3DProjectDbResult result = execute(db, "BEGIN IMMEDIATE", "begin feature support");
|
|
sqlite3_stmt *s = NULL;
|
|
bool inserted_support_set = false;
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = validate_support_sources_locked(db, config, groups, count);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = prepare(db, "INSERT OR IGNORE INTO feature_support_sets(image_id,first_feature_set_id,"
|
|
"second_feature_set_id,radius_pixels,parameter_fingerprint,created_at) "
|
|
"SELECT ?1,?2,?3,?4,?5,?6 WHERE EXISTS(SELECT 1 FROM feature_sets a,"
|
|
"feature_sets b WHERE a.feature_set_id=?2 AND b.feature_set_id=?3 AND "
|
|
"a.image_id=?1 AND b.image_id=?1)", &s);
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(s, 1, (sqlite3_int64)config->image_id);
|
|
sqlite3_bind_int64(s, 2, (sqlite3_int64)config->first_feature_set_id);
|
|
sqlite3_bind_int64(s, 3, (sqlite3_int64)config->second_feature_set_id);
|
|
sqlite3_bind_double(s, 4, config->radius_pixels);
|
|
sqlite3_bind_blob(s, 5, config->parameter_fingerprint, 32, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int64(s, 6, config->created_at);
|
|
result = step_done(db, s, "insert feature support set");
|
|
inserted_support_set = result == LARDON3D_PROJECT_DB_OK &&
|
|
sqlite3_changes(db->connection) == 1;
|
|
}
|
|
sqlite3_int64 support_id = 0;
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = prepare(db, "SELECT feature_support_set_id FROM feature_support_sets WHERE image_id=?1 "
|
|
"AND first_feature_set_id=?2 AND second_feature_set_id=?3 AND "
|
|
"parameter_fingerprint=?4", &s);
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(s, 1, (sqlite3_int64)config->image_id);
|
|
sqlite3_bind_int64(s, 2, (sqlite3_int64)config->first_feature_set_id);
|
|
sqlite3_bind_int64(s, 3, (sqlite3_int64)config->second_feature_set_id);
|
|
sqlite3_bind_blob(s, 4, config->parameter_fingerprint, 32, SQLITE_TRANSIENT);
|
|
int step = sqlite3_step(s);
|
|
if (step == SQLITE_ROW) support_id = sqlite3_column_int64(s, 0);
|
|
else result = step == SQLITE_DONE ? LARDON3D_PROJECT_DB_NOT_FOUND
|
|
: sqlite_result(db, step, "select feature support");
|
|
sqlite3_finalize(s);
|
|
}
|
|
for (size_t i = 0; inserted_support_set && result == LARDON3D_PROJECT_DB_OK && i < count; ++i) {
|
|
result = prepare(db, "INSERT INTO feature_support_groups(feature_support_set_id,x,y,"
|
|
"distance_pixels,support_count) VALUES(?1,?2,?3,?4,?5)", &s);
|
|
if (result != LARDON3D_PROJECT_DB_OK) break;
|
|
sqlite3_bind_int64(s, 1, support_id);
|
|
sqlite3_bind_double(s, 2, groups[i].x);
|
|
sqlite3_bind_double(s, 3, groups[i].y);
|
|
sqlite3_bind_double(s, 4, groups[i].distance_pixels);
|
|
sqlite3_bind_int64(s, 5, groups[i].support_count);
|
|
result = step_done(db, s, "insert feature support group");
|
|
sqlite3_int64 group_id = sqlite3_last_insert_rowid(db->connection);
|
|
for (unsigned member = 0; result == LARDON3D_PROJECT_DB_OK && member < groups[i].support_count;
|
|
++member) {
|
|
result = prepare(db, "INSERT INTO feature_support_members(feature_support_group_id,"
|
|
"feature_set_id,feature_index,extractor_kind) SELECT ?1,?2,?3,"
|
|
"extractor_kind FROM feature_sets WHERE feature_set_id=?2", &s);
|
|
if (result != LARDON3D_PROJECT_DB_OK) break;
|
|
sqlite3_bind_int64(s, 1, group_id);
|
|
bool from_second = member != 0 || groups[i].first_member_from_second_set;
|
|
sqlite3_bind_int64(s, 2, (sqlite3_int64)(from_second ? config->second_feature_set_id
|
|
: config->first_feature_set_id));
|
|
sqlite3_bind_int64(s, 3, member ? groups[i].second_feature_index
|
|
: groups[i].first_feature_index);
|
|
result = step_done(db, s, "insert feature support member");
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) result = execute(db, "COMMIT", "commit feature support");
|
|
if (result != LARDON3D_PROJECT_DB_OK) (void)execute(db, "ROLLBACK", "rollback feature support");
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result == LARDON3D_PROJECT_DB_OK
|
|
? lardon3d_project_db_load_feature_support(
|
|
db, config->image_id, config->first_feature_set_id,
|
|
config->second_feature_set_id, config->parameter_fingerprint, out)
|
|
: result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_register_feature_set(
|
|
Lardon3DProjectDb *db, uint64_t image_id, const char *kind, uint32_t version,
|
|
const unsigned char fp[32], const unsigned char source[32], uint32_t count, uint32_t type,
|
|
uint32_t dim, const unsigned char hash[32], const char *path, uint64_t size,
|
|
Lardon3DProjectDbFeatureDurability durability, uint64_t producer, int64_t created,
|
|
Lardon3DProjectDbFeatureSet *out) {
|
|
return lardon3d_project_db_register_feature_set_quality(
|
|
db, image_id, kind, version, fp, source, count, type, dim, 0, 0, 0.0, 0.0, hash, path, size,
|
|
durability, producer, created, out);
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_find_feature_set(Lardon3DProjectDb *db,
|
|
uint64_t image_id, const char *kind,
|
|
uint32_t version,
|
|
const unsigned char fp[32],
|
|
Lardon3DProjectDbFeatureSet *out) {
|
|
if (!db || !valid_catalog_id(image_id) || !lardon3d_task_kind_is_valid(kind) || version == 0 ||
|
|
!fp || !out) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
char sql[1024];
|
|
snprintf(sql, sizeof(sql),
|
|
"%s WHERE f.image_id=?1 AND f.extractor_kind=?2 AND f.extractor_version=?3 AND "
|
|
"f.parameter_fingerprint=?4",
|
|
feature_select);
|
|
sqlite3_stmt *s = NULL;
|
|
Lardon3DProjectDbResult result = prepare(db, sql, &s);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(s, 1, (sqlite3_int64)image_id);
|
|
sqlite3_bind_text(s, 2, kind, -1, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int64(s, 3, version);
|
|
sqlite3_bind_blob(s, 4, fp, 32, SQLITE_TRANSIENT);
|
|
int code = sqlite3_step(s);
|
|
if (code == SQLITE_DONE) {
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
} else if (code != SQLITE_ROW || !read_feature_set(s, out)) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
sqlite3_finalize(s);
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_load_feature_set(Lardon3DProjectDb *db, uint64_t id,
|
|
Lardon3DProjectDbFeatureSet *out) {
|
|
if (!db || !valid_catalog_id(id) || !out) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
char sql[1024];
|
|
snprintf(sql, sizeof(sql), "%s WHERE f.feature_set_id=?1", feature_select);
|
|
sqlite3_stmt *s = NULL;
|
|
Lardon3DProjectDbResult result = prepare(db, sql, &s);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(s, 1, (sqlite3_int64)id);
|
|
int code = sqlite3_step(s);
|
|
if (code == SQLITE_DONE) {
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
} else if (code != SQLITE_ROW || !read_feature_set(s, out)) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
sqlite3_finalize(s);
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_list_feature_sets(Lardon3DProjectDb *db, uint64_t after,
|
|
Lardon3DProjectDbFeatureSet *sets,
|
|
size_t capacity, size_t *count) {
|
|
if (count) {
|
|
*count = 0;
|
|
}
|
|
if (!db || after > INT64_MAX || !sets || !count || capacity == 0 || capacity > 256) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
char sql[1024];
|
|
snprintf(sql, sizeof(sql), "%s WHERE f.feature_set_id>?1 ORDER BY f.feature_set_id LIMIT ?2",
|
|
feature_select);
|
|
sqlite3_stmt *s = NULL;
|
|
Lardon3DProjectDbResult result = prepare(db, sql, &s);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(s, 1, (sqlite3_int64)after);
|
|
sqlite3_bind_int64(s, 2, (sqlite3_int64)capacity);
|
|
int code = SQLITE_DONE;
|
|
while (*count < capacity && (code = sqlite3_step(s)) == SQLITE_ROW) {
|
|
if (!read_feature_set(s, &sets[*count])) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
break;
|
|
}
|
|
++*count;
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && *count < capacity && code != SQLITE_DONE) {
|
|
result = sqlite_result(db, code, "list feature sets");
|
|
}
|
|
sqlite3_finalize(s);
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult
|
|
lardon3d_project_db_load_feature_extract_task(Lardon3DProjectDb *db, uint64_t task_id,
|
|
Lardon3DProjectDbFeatureExtractTask *p) {
|
|
if (!db || !valid_task_id(task_id) || !p) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
memset(p, 0, sizeof(*p));
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
sqlite3_stmt *s = NULL;
|
|
Lardon3DProjectDbResult result =
|
|
prepare(db,
|
|
"SELECT "
|
|
"image_id,extractor_kind,extractor_version,max_features,pyramid_levels,fast_"
|
|
"threshold,parameter_fingerprint FROM feature_extract_tasks WHERE task_id=?1",
|
|
&s);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(s, 1, (sqlite3_int64)task_id);
|
|
int code = sqlite3_step(s);
|
|
sqlite3_int64 image = sqlite3_column_int64(s, 0), version = sqlite3_column_int64(s, 2),
|
|
max = sqlite3_column_int64(s, 3), levels = sqlite3_column_int64(s, 4),
|
|
threshold = sqlite3_column_int64(s, 5);
|
|
const void *fp = sqlite3_column_blob(s, 6);
|
|
if (code == SQLITE_DONE) {
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
} else if (code != SQLITE_ROW || image <= 0 || version <= 0 || version > UINT32_MAX ||
|
|
max <= 0 || max > 8192 || levels <= 0 || levels > 16 || threshold <= 0 ||
|
|
threshold > 255 ||
|
|
!copy_column(s, 1, p->extractor_kind, sizeof(p->extractor_kind)) ||
|
|
!lardon3d_task_kind_is_valid(p->extractor_kind) ||
|
|
sqlite3_column_bytes(s, 6) != 32 || !fp) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
} else {
|
|
p->task_id = task_id;
|
|
p->image_id = (uint64_t)image;
|
|
p->extractor_version = (uint32_t)version;
|
|
p->max_features = (uint32_t)max;
|
|
p->pyramid_levels = (uint32_t)levels;
|
|
p->fast_threshold = (uint32_t)threshold;
|
|
memcpy(p->parameter_fingerprint, fp, 32);
|
|
}
|
|
sqlite3_finalize(s);
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_load_sift_extract_task(
|
|
Lardon3DProjectDb *db, uint64_t task_id, Lardon3DProjectDbSiftExtractTask *p) {
|
|
if (!db || !valid_task_id(task_id) || !p) return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
memset(p, 0, sizeof(*p));
|
|
pthread_mutex_lock(&db->mutex);
|
|
sqlite3_stmt *s = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
db, "SELECT image_id,extractor_kind,extractor_version,max_features,octave_layers,"
|
|
"contrast_threshold,edge_threshold,sigma,grid_rows,grid_cols,max_features_per_cell,"
|
|
"parameter_fingerprint FROM sift_extract_tasks WHERE task_id=?1", &s);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(s, 1, (sqlite3_int64)task_id);
|
|
int code = sqlite3_step(s);
|
|
if (code == SQLITE_DONE) {
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
} else if (code != SQLITE_ROW || sqlite3_column_int64(s, 0) <= 0 ||
|
|
!copy_column(s, 1, p->extractor_kind, sizeof(p->extractor_kind)) ||
|
|
sqlite3_column_bytes(s, 11) != 32) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
} else {
|
|
p->task_id = task_id;
|
|
p->image_id = (uint64_t)sqlite3_column_int64(s, 0);
|
|
p->extractor_version = (uint32_t)sqlite3_column_int64(s, 2);
|
|
p->max_features = (uint32_t)sqlite3_column_int64(s, 3);
|
|
p->octave_layers = (uint32_t)sqlite3_column_int64(s, 4);
|
|
p->contrast_threshold = sqlite3_column_double(s, 5);
|
|
p->edge_threshold = sqlite3_column_double(s, 6);
|
|
p->sigma = sqlite3_column_double(s, 7);
|
|
p->grid_rows = (uint32_t)sqlite3_column_int64(s, 8);
|
|
p->grid_cols = (uint32_t)sqlite3_column_int64(s, 9);
|
|
p->max_features_per_cell = (uint32_t)sqlite3_column_int64(s, 10);
|
|
memcpy(p->parameter_fingerprint, sqlite3_column_blob(s, 11), 32);
|
|
}
|
|
sqlite3_finalize(s);
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK &&
|
|
((strcmp(p->extractor_kind, "sift") != 0 && strcmp(p->extractor_kind, "rootsift") != 0) ||
|
|
p->extractor_version != 1 || p->max_features < 1 || p->max_features > 8192 ||
|
|
p->octave_layers < 1 || p->octave_layers > 8 || !isfinite(p->contrast_threshold) ||
|
|
p->contrast_threshold < 0.001 || p->contrast_threshold > 0.2 ||
|
|
!isfinite(p->edge_threshold) || p->edge_threshold < 1.0 || p->edge_threshold > 100.0 ||
|
|
!isfinite(p->sigma) || p->sigma < 0.5 || p->sigma > 3.0 || p->grid_rows < 1 ||
|
|
p->grid_rows > 32 || p->grid_cols < 1 || p->grid_cols > 32 ||
|
|
p->max_features_per_cell < 1 || p->max_features_per_cell > 8192)) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
static bool valid_visual_index(const Lardon3DProjectDbVisualIndex *index) {
|
|
return index && lardon3d_task_kind_is_valid(index->index_kind) && index->index_version > 0 &&
|
|
index->descriptor_type >= 1 && index->descriptor_type <= 2 &&
|
|
index->descriptor_dimension > 0 && index->descriptor_dimension <= 4096 &&
|
|
lardon3d_task_kind_is_valid(index->extractor_kind) && index->extractor_version > 0 &&
|
|
index->table_count > 0 && index->table_count <= 32 && index->key_bits >= 8 &&
|
|
index->key_bits <= 32 && index->max_features_per_set > 0 &&
|
|
index->max_features_per_set <= 1024 && index->max_bucket_postings > 0 &&
|
|
index->max_bucket_postings <= 4096 && index->created_at >= 0;
|
|
}
|
|
|
|
static bool read_visual_index(sqlite3_stmt *statement, Lardon3DProjectDbVisualIndex *index) {
|
|
memset(index, 0, sizeof(*index));
|
|
sqlite3_int64 id = sqlite3_column_int64(statement, 0);
|
|
const void *feature_fp = sqlite3_column_blob(statement, 7);
|
|
const void *index_fp = sqlite3_column_blob(statement, 8);
|
|
if (id <= 0 || !copy_column(statement, 1, index->index_kind, sizeof(index->index_kind)) ||
|
|
!copy_column(statement, 5, index->extractor_kind, sizeof(index->extractor_kind)) ||
|
|
sqlite3_column_bytes(statement, 7) != 32 || sqlite3_column_bytes(statement, 8) != 32 ||
|
|
!feature_fp || !index_fp) {
|
|
return false;
|
|
}
|
|
index->visual_index_id = (uint64_t)id;
|
|
index->index_version = (uint32_t)sqlite3_column_int64(statement, 2);
|
|
index->descriptor_type = (uint32_t)sqlite3_column_int64(statement, 3);
|
|
index->descriptor_dimension = (uint32_t)sqlite3_column_int64(statement, 4);
|
|
index->extractor_version = (uint32_t)sqlite3_column_int64(statement, 6);
|
|
memcpy(index->feature_parameter_fingerprint, feature_fp, 32);
|
|
memcpy(index->index_parameter_fingerprint, index_fp, 32);
|
|
index->table_count = (uint32_t)sqlite3_column_int64(statement, 9);
|
|
index->key_bits = (uint32_t)sqlite3_column_int64(statement, 10);
|
|
index->max_features_per_set = (uint32_t)sqlite3_column_int64(statement, 11);
|
|
index->max_bucket_postings = (uint32_t)sqlite3_column_int64(statement, 12);
|
|
index->created_at = sqlite3_column_int64(statement, 13);
|
|
return valid_visual_index(index);
|
|
}
|
|
|
|
static const char visual_index_select[] =
|
|
"SELECT visual_index_id,index_kind,index_version,descriptor_type,descriptor_dimension,"
|
|
"extractor_kind,extractor_version,feature_parameter_fingerprint,"
|
|
"index_parameter_fingerprint,table_count,key_bits,max_features_per_set,"
|
|
"max_bucket_postings,created_at FROM visual_indexes";
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_create_visual_index(
|
|
Lardon3DProjectDb *db, const Lardon3DProjectDbVisualIndex *configuration,
|
|
Lardon3DProjectDbVisualIndex *out) {
|
|
if (!db || !valid_visual_index(configuration) || !out) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
db,
|
|
"INSERT INTO visual_indexes(index_kind,index_version,descriptor_type,descriptor_dimension,"
|
|
"extractor_kind,extractor_version,feature_parameter_fingerprint,"
|
|
"index_parameter_fingerprint,table_count,key_bits,max_features_per_set,"
|
|
"max_bucket_postings,created_at) VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13) "
|
|
"ON CONFLICT(index_kind,index_version,descriptor_type,descriptor_dimension,extractor_kind,"
|
|
"extractor_version,feature_parameter_fingerprint,index_parameter_fingerprint) DO NOTHING",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_text(statement, 1, configuration->index_kind, -1, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int64(statement, 2, configuration->index_version);
|
|
sqlite3_bind_int64(statement, 3, configuration->descriptor_type);
|
|
sqlite3_bind_int64(statement, 4, configuration->descriptor_dimension);
|
|
sqlite3_bind_text(statement, 5, configuration->extractor_kind, -1, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int64(statement, 6, configuration->extractor_version);
|
|
sqlite3_bind_blob(statement, 7, configuration->feature_parameter_fingerprint, 32,
|
|
SQLITE_TRANSIENT);
|
|
sqlite3_bind_blob(statement, 8, configuration->index_parameter_fingerprint, 32,
|
|
SQLITE_TRANSIENT);
|
|
sqlite3_bind_int64(statement, 9, configuration->table_count);
|
|
sqlite3_bind_int64(statement, 10, configuration->key_bits);
|
|
sqlite3_bind_int64(statement, 11, configuration->max_features_per_set);
|
|
sqlite3_bind_int64(statement, 12, configuration->max_bucket_postings);
|
|
sqlite3_bind_int64(statement, 13, configuration->created_at);
|
|
result = step_done(db, statement, "create visual index");
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
char sql[1024];
|
|
snprintf(sql, sizeof(sql),
|
|
"%s WHERE index_kind=?1 AND index_version=?2 AND descriptor_type=?3 AND "
|
|
"descriptor_dimension=?4 AND extractor_kind=?5 AND extractor_version=?6 AND "
|
|
"feature_parameter_fingerprint=?7 AND index_parameter_fingerprint=?8",
|
|
visual_index_select);
|
|
result = prepare(db, sql, &statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_text(statement, 1, configuration->index_kind, -1, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int64(statement, 2, configuration->index_version);
|
|
sqlite3_bind_int64(statement, 3, configuration->descriptor_type);
|
|
sqlite3_bind_int64(statement, 4, configuration->descriptor_dimension);
|
|
sqlite3_bind_text(statement, 5, configuration->extractor_kind, -1, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int64(statement, 6, configuration->extractor_version);
|
|
sqlite3_bind_blob(statement, 7, configuration->feature_parameter_fingerprint, 32,
|
|
SQLITE_TRANSIENT);
|
|
sqlite3_bind_blob(statement, 8, configuration->index_parameter_fingerprint, 32,
|
|
SQLITE_TRANSIENT);
|
|
int code = sqlite3_step(statement);
|
|
if (code != SQLITE_ROW || !read_visual_index(statement, out)) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
sqlite3_finalize(statement);
|
|
}
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_load_visual_index(
|
|
Lardon3DProjectDb *db, uint64_t id, Lardon3DProjectDbVisualIndex *out) {
|
|
if (!db || !valid_catalog_id(id) || !out) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
char sql[768];
|
|
snprintf(sql, sizeof(sql), "%s WHERE visual_index_id=?1", visual_index_select);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(db, sql, &statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)id);
|
|
int code = sqlite3_step(statement);
|
|
result = code == SQLITE_DONE ? LARDON3D_PROJECT_DB_NOT_FOUND
|
|
: code == SQLITE_ROW && read_visual_index(statement, out)
|
|
? LARDON3D_PROJECT_DB_OK
|
|
: LARDON3D_PROJECT_DB_CORRUPT;
|
|
sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
static bool canonical_visual_path(const unsigned char hash[32], const char *path) {
|
|
static const char digits[] = "0123456789abcdef";
|
|
char hex[65];
|
|
for (size_t i = 0; i < 32; ++i) {
|
|
hex[i * 2] = digits[hash[i] >> 4];
|
|
hex[i * 2 + 1] = digits[hash[i] & 15];
|
|
}
|
|
hex[64] = '\0';
|
|
char expected[LARDON3D_PROJECT_DB_PATH_CAPACITY];
|
|
int written = snprintf(expected, sizeof(expected), "assets/visual-index/%c%c/%s", hex[0],
|
|
hex[1], hex);
|
|
return written > 0 && (size_t)written < sizeof(expected) && strcmp(expected, path) == 0;
|
|
}
|
|
|
|
static bool read_visual_segment(sqlite3_stmt *statement,
|
|
Lardon3DProjectDbVisualIndexSegment *segment) {
|
|
memset(segment, 0, sizeof(*segment));
|
|
sqlite3_int64 id = sqlite3_column_int64(statement, 0);
|
|
sqlite3_int64 index_id = sqlite3_column_int64(statement, 1);
|
|
sqlite3_int64 generation = sqlite3_column_int64(statement, 2);
|
|
const void *hash = sqlite3_column_blob(statement, 3);
|
|
sqlite3_int64 size = sqlite3_column_int64(statement, 5);
|
|
sqlite3_int64 postings = sqlite3_column_int64(statement, 6);
|
|
sqlite3_int64 members = sqlite3_column_int64(statement, 7);
|
|
sqlite3_int64 durability = sqlite3_column_int64(statement, 8);
|
|
if (id <= 0 || index_id <= 0 || generation <= 0 || !hash ||
|
|
sqlite3_column_bytes(statement, 3) != 32 || size < 128 || postings < 0 || members < 1 ||
|
|
members > 16 || durability < 0 || durability > 1 ||
|
|
!copy_column(statement, 4, segment->path, sizeof(segment->path))) {
|
|
return false;
|
|
}
|
|
segment->visual_index_segment_id = (uint64_t)id;
|
|
segment->visual_index_id = (uint64_t)index_id;
|
|
segment->generation = (uint64_t)generation;
|
|
memcpy(segment->sha256, hash, 32);
|
|
segment->size_bytes = (uint64_t)size;
|
|
segment->posting_count = (uint64_t)postings;
|
|
segment->feature_set_count = (uint32_t)members;
|
|
segment->durability = (Lardon3DProjectDbVisualIndexDurability)durability;
|
|
segment->has_producer_task = sqlite3_column_type(statement, 9) != SQLITE_NULL;
|
|
segment->producer_task_id = segment->has_producer_task
|
|
? (uint64_t)sqlite3_column_int64(statement, 9)
|
|
: 0;
|
|
segment->created_at = sqlite3_column_int64(statement, 10);
|
|
return segment->created_at >= 0 && canonical_visual_path(segment->sha256, segment->path);
|
|
}
|
|
|
|
static const char visual_segment_select[] =
|
|
"SELECT visual_index_segment_id,visual_index_id,generation,sha256,path,size_bytes,"
|
|
"posting_count,feature_set_count,durability,producer_task_id,created_at "
|
|
"FROM visual_index_segments";
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_list_visual_index_segments(
|
|
Lardon3DProjectDb *db, uint64_t index_id, uint64_t after,
|
|
Lardon3DProjectDbVisualIndexSegment *segments, size_t capacity, size_t *count) {
|
|
if (count) {
|
|
*count = 0;
|
|
}
|
|
if (!db || !valid_catalog_id(index_id) || after > INT64_MAX || !segments || !count ||
|
|
capacity == 0 || capacity > 256) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
char sql[768];
|
|
snprintf(sql, sizeof(sql), "%s WHERE visual_index_id=?1 AND generation>?2 ORDER BY generation "
|
|
"LIMIT ?3", visual_segment_select);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(db, sql, &statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)index_id);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)after);
|
|
sqlite3_bind_int64(statement, 3, (sqlite3_int64)capacity);
|
|
int code = SQLITE_DONE;
|
|
while (*count < capacity && (code = sqlite3_step(statement)) == SQLITE_ROW) {
|
|
if (!read_visual_segment(statement, &segments[*count])) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
break;
|
|
}
|
|
++*count;
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && code != SQLITE_DONE && *count < capacity) {
|
|
result = sqlite_result(db, code, "list visual index segments");
|
|
}
|
|
sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_list_visual_index_pending(
|
|
Lardon3DProjectDb *db, uint64_t index_id, uint64_t after,
|
|
Lardon3DProjectDbFeatureSet *sets, size_t capacity, size_t *count) {
|
|
if (count) {
|
|
*count = 0;
|
|
}
|
|
if (!db || !valid_catalog_id(index_id) || after > INT64_MAX || !sets || !count ||
|
|
capacity == 0 || capacity > 16) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
char sql[2048];
|
|
snprintf(sql, sizeof(sql),
|
|
"%s JOIN visual_indexes v ON v.visual_index_id=?1 LEFT JOIN "
|
|
"visual_index_memberships m ON m.visual_index_id=v.visual_index_id AND "
|
|
"m.feature_set_id=f.feature_set_id WHERE f.feature_set_id>?2 AND m.feature_set_id IS "
|
|
"NULL AND f.descriptor_type=v.descriptor_type AND f.descriptor_dimension="
|
|
"v.descriptor_dimension AND f.extractor_kind=v.extractor_kind AND "
|
|
"f.extractor_version=v.extractor_version AND f.parameter_fingerprint="
|
|
"v.feature_parameter_fingerprint ORDER BY f.feature_set_id LIMIT ?3",
|
|
feature_select);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(db, sql, &statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)index_id);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)after);
|
|
sqlite3_bind_int64(statement, 3, (sqlite3_int64)capacity);
|
|
int code = SQLITE_DONE;
|
|
while (*count < capacity && (code = sqlite3_step(statement)) == SQLITE_ROW) {
|
|
if (!read_feature_set(statement, &sets[*count])) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
break;
|
|
}
|
|
++*count;
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK && code != SQLITE_DONE && *count < capacity) {
|
|
result = sqlite_result(db, code, "list pending visual index features");
|
|
}
|
|
sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_publish_visual_index_segment(
|
|
Lardon3DProjectDb *db, const Lardon3DProjectDbVisualIndexSegment *segment,
|
|
const uint64_t *ids, size_t count, Lardon3DProjectDbVisualIndexSegment *published) {
|
|
if (!db || !segment || !valid_catalog_id(segment->visual_index_id) || segment->generation == 0 ||
|
|
!canonical_visual_path(segment->sha256, segment->path) || segment->size_bytes < 128 ||
|
|
segment->size_bytes > INT64_MAX || segment->posting_count > INT64_MAX || !ids || count == 0 ||
|
|
count > 16 || segment->feature_set_count != count || segment->durability < 0 ||
|
|
segment->durability > 1 || (segment->producer_task_id &&
|
|
!valid_task_id(segment->producer_task_id)) ||
|
|
segment->created_at < 0 || !published) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
|
const char *fail_publish = getenv("LARDON3D_TEST_PROJECT_DB_FAIL_VISUAL_SEGMENT");
|
|
if (fail_publish && strcmp(fail_publish, "1") == 0) {
|
|
return LARDON3D_PROJECT_DB_BUSY;
|
|
}
|
|
#endif
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
Lardon3DProjectDbResult result = execute(db, "BEGIN IMMEDIATE", "begin visual segment publish");
|
|
sqlite3_stmt *statement = NULL;
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = prepare(db, "INSERT INTO visual_index_segments(visual_index_id,generation,sha256,"
|
|
"path,size_bytes,posting_count,feature_set_count,durability,"
|
|
"producer_task_id,created_at) VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10)",
|
|
&statement);
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)segment->visual_index_id);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)segment->generation);
|
|
sqlite3_bind_blob(statement, 3, segment->sha256, 32, SQLITE_TRANSIENT);
|
|
sqlite3_bind_text(statement, 4, segment->path, -1, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int64(statement, 5, (sqlite3_int64)segment->size_bytes);
|
|
sqlite3_bind_int64(statement, 6, (sqlite3_int64)segment->posting_count);
|
|
sqlite3_bind_int64(statement, 7, (sqlite3_int64)count);
|
|
sqlite3_bind_int(statement, 8, (int)segment->durability);
|
|
if (segment->producer_task_id) {
|
|
sqlite3_bind_int64(statement, 9, (sqlite3_int64)segment->producer_task_id);
|
|
} else {
|
|
sqlite3_bind_null(statement, 9);
|
|
}
|
|
sqlite3_bind_int64(statement, 10, segment->created_at);
|
|
result = step_done(db, statement, "insert visual segment");
|
|
}
|
|
sqlite3_int64 segment_id = sqlite3_last_insert_rowid(db->connection);
|
|
for (size_t i = 0; i < count && result == LARDON3D_PROJECT_DB_OK; ++i) {
|
|
if (!valid_catalog_id(ids[i])) {
|
|
result = LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
break;
|
|
}
|
|
result = prepare(db, "INSERT INTO visual_index_memberships(visual_index_id,feature_set_id,"
|
|
"visual_index_segment_id) VALUES(?1,?2,?3)", &statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)segment->visual_index_id);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)ids[i]);
|
|
sqlite3_bind_int64(statement, 3, segment_id);
|
|
result = step_done(db, statement, "insert visual membership");
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = execute(db, "COMMIT", "commit visual segment publish");
|
|
}
|
|
if (result != LARDON3D_PROJECT_DB_OK) {
|
|
(void)execute(db, "ROLLBACK", "rollback visual segment publish");
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
if (result != LARDON3D_PROJECT_DB_OK) {
|
|
return result;
|
|
}
|
|
Lardon3DProjectDbVisualIndexSegment page[1];
|
|
size_t page_count = 0;
|
|
result = lardon3d_project_db_list_visual_index_segments(db, segment->visual_index_id,
|
|
segment->generation - 1, page, 1,
|
|
&page_count);
|
|
if (result == LARDON3D_PROJECT_DB_OK && page_count == 1 &&
|
|
page[0].generation == segment->generation) {
|
|
*published = page[0];
|
|
return LARDON3D_PROJECT_DB_OK;
|
|
}
|
|
return result == LARDON3D_PROJECT_DB_OK ? LARDON3D_PROJECT_DB_CORRUPT : result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_record_visual_index_update_task(
|
|
Lardon3DProjectDb *db, const Lardon3DTaskDurableSnapshot *snapshot, const char *kind,
|
|
uint32_t version, const Lardon3DProjectDbCheckpoint *checkpoint,
|
|
const Lardon3DProjectDbVisualIndexUpdateTask *parameters, int64_t updated_at) {
|
|
if (!snapshot || !parameters || parameters->task_id != snapshot->id ||
|
|
!valid_catalog_id(parameters->visual_index_id) ||
|
|
parameters->after_feature_set_id > INT64_MAX) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
return record_task_internal(db, snapshot, kind, version, checkpoint, NULL, 0, NULL,
|
|
NULL, parameters, NULL, NULL, NULL, updated_at);
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_load_visual_index_update_task(
|
|
Lardon3DProjectDb *db, uint64_t task_id,
|
|
Lardon3DProjectDbVisualIndexUpdateTask *parameters) {
|
|
if (!db || !valid_task_id(task_id) || !parameters) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
memset(parameters, 0, sizeof(*parameters));
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
db, "SELECT visual_index_id,after_feature_set_id FROM visual_index_update_tasks WHERE "
|
|
"task_id=?1", &statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)task_id);
|
|
int code = sqlite3_step(statement);
|
|
sqlite3_int64 index_id = sqlite3_column_int64(statement, 0);
|
|
sqlite3_int64 after = sqlite3_column_int64(statement, 1);
|
|
if (code == SQLITE_DONE) {
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
} else if (code != SQLITE_ROW || index_id <= 0 || after < 0) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
} else {
|
|
parameters->task_id = task_id;
|
|
parameters->visual_index_id = (uint64_t)index_id;
|
|
parameters->after_feature_set_id = (uint64_t)after;
|
|
}
|
|
sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_record_candidate_pair_generate_task(
|
|
Lardon3DProjectDb *db, const Lardon3DTaskDurableSnapshot *snapshot, const char *kind,
|
|
uint32_t version, const Lardon3DProjectDbCheckpoint *checkpoint,
|
|
const Lardon3DProjectDbCandidatePairGenerateTask *parameters, int64_t updated_at) {
|
|
if (!snapshot || !parameters || parameters->task_id != snapshot->id ||
|
|
!valid_catalog_id(parameters->visual_index_id) ||
|
|
parameters->after_feature_set_id > INT64_MAX || parameters->top_k == 0 ||
|
|
parameters->top_k > 256 || parameters->minimum_evidence_count > 1024 ||
|
|
parameters->scanset_filter < 0 || parameters->scanset_filter > 2) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
return record_task_internal(db, snapshot, kind, version, checkpoint, NULL, 0, NULL,
|
|
NULL, NULL, parameters, NULL, NULL, updated_at);
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_load_candidate_pair_generate_task(
|
|
Lardon3DProjectDb *db, uint64_t task_id,
|
|
Lardon3DProjectDbCandidatePairGenerateTask *parameters) {
|
|
if (!db || !valid_task_id(task_id) || !parameters) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
memset(parameters, 0, sizeof(*parameters));
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
db,
|
|
"SELECT visual_index_id,after_feature_set_id,top_k,minimum_evidence_count,"
|
|
"scanset_filter,exclude_same_asset FROM candidate_pair_generate_tasks WHERE task_id=?1",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)task_id);
|
|
int code = sqlite3_step(statement);
|
|
sqlite3_int64 index_id = sqlite3_column_int64(statement, 0);
|
|
sqlite3_int64 after = sqlite3_column_int64(statement, 1);
|
|
int top_k_val = sqlite3_column_int(statement, 2);
|
|
int evidence = sqlite3_column_int(statement, 3);
|
|
int filter_val = sqlite3_column_int(statement, 4);
|
|
int same_asset = sqlite3_column_int(statement, 5);
|
|
if (code == SQLITE_DONE) {
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
} else if (code != SQLITE_ROW || index_id <= 0 || after < 0 || top_k_val <= 0 ||
|
|
top_k_val > 256 || evidence < 0 || evidence > 1024 || filter_val < 0 ||
|
|
filter_val > 2) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
} else {
|
|
parameters->task_id = task_id;
|
|
parameters->visual_index_id = (uint64_t)index_id;
|
|
parameters->after_feature_set_id = (uint64_t)after;
|
|
parameters->top_k = (uint32_t)top_k_val;
|
|
parameters->minimum_evidence_count = (uint32_t)evidence;
|
|
parameters->scanset_filter = filter_val;
|
|
parameters->exclude_same_asset = same_asset != 0;
|
|
}
|
|
sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_record_matcher_task(
|
|
Lardon3DProjectDb *db, const Lardon3DTaskDurableSnapshot *snapshot,
|
|
const char *kind, uint32_t version,
|
|
const Lardon3DProjectDbCheckpoint *checkpoint,
|
|
const Lardon3DProjectDbMatcherTask *parameters, int64_t updated_at) {
|
|
if (!snapshot || !parameters || parameters->task_id != snapshot->id) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
return record_task_internal(db, snapshot, kind, version, checkpoint, NULL, 0, NULL,
|
|
NULL, NULL, NULL, parameters, NULL, updated_at);
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_load_matcher_task(
|
|
Lardon3DProjectDb *db, uint64_t task_id,
|
|
Lardon3DProjectDbMatcherTask *parameters) {
|
|
if (!db || !valid_task_id(task_id) || !parameters) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
memset(parameters, 0, sizeof(*parameters));
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
db,
|
|
"SELECT after_candidate_pair_id,feature_extractor_kind,feature_extractor_version,"
|
|
"feature_parameter_fingerprint,matcher_kind,ratio_threshold FROM matcher_tasks "
|
|
"WHERE task_id=?1",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)task_id);
|
|
int code = sqlite3_step(statement);
|
|
sqlite3_int64 after = sqlite3_column_int64(statement, 0);
|
|
sqlite3_int64 extractor_version = sqlite3_column_int64(statement, 2);
|
|
int matcher_kind = sqlite3_column_int(statement, 4);
|
|
double ratio = sqlite3_column_double(statement, 5);
|
|
if (code == SQLITE_DONE) {
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
} else if (code != SQLITE_ROW || after < 0 || extractor_version <= 0 ||
|
|
extractor_version > UINT32_MAX || matcher_kind < 0 || matcher_kind > 2 ||
|
|
!isfinite(ratio) || ratio <= 0.0 || ratio >= 1.0 ||
|
|
sqlite3_column_bytes(statement, 3) != LARDON3D_PROJECT_DB_SHA256_SIZE ||
|
|
!copy_column(statement, 1, parameters->feature_extractor_kind,
|
|
sizeof(parameters->feature_extractor_kind))) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
} else {
|
|
parameters->task_id = task_id;
|
|
parameters->after_candidate_pair_id = (uint64_t)after;
|
|
parameters->feature_extractor_version = (uint32_t)extractor_version;
|
|
memcpy(parameters->feature_parameter_fingerprint,
|
|
sqlite3_column_blob(statement, 3), LARDON3D_PROJECT_DB_SHA256_SIZE);
|
|
parameters->matcher_kind = matcher_kind;
|
|
parameters->ratio_threshold = (float)ratio;
|
|
}
|
|
sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_record_geometric_verifier_task(
|
|
Lardon3DProjectDb *db, const Lardon3DTaskDurableSnapshot *snapshot,
|
|
const char *kind, uint32_t version,
|
|
const Lardon3DProjectDbCheckpoint *checkpoint,
|
|
const Lardon3DProjectDbGeometricVerifierTask *parameters, int64_t updated_at) {
|
|
if (!snapshot || !parameters || parameters->task_id != snapshot->id) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
return record_task_internal(db, snapshot, kind, version, checkpoint, NULL, 0, NULL,
|
|
NULL, NULL, NULL, NULL, parameters, updated_at);
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_load_geometric_verifier_task(
|
|
Lardon3DProjectDb *db, uint64_t task_id,
|
|
Lardon3DProjectDbGeometricVerifierTask *parameters) {
|
|
if (!db || !valid_task_id(task_id) || !parameters) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
memset(parameters, 0, sizeof(*parameters));
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
db,
|
|
"SELECT after_match_result_id,threshold_pixels,confidence,max_iterations,"
|
|
"min_inlier_count,min_inlier_ratio,seed_policy_version,canonicalization_version,"
|
|
"parameter_fingerprint FROM geometric_verifier_tasks WHERE task_id=?1",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)task_id);
|
|
int code = sqlite3_step(statement);
|
|
sqlite3_int64 after = sqlite3_column_int64(statement, 0);
|
|
double threshold = sqlite3_column_double(statement, 1);
|
|
double confidence = sqlite3_column_double(statement, 2);
|
|
sqlite3_int64 iterations = sqlite3_column_int64(statement, 3);
|
|
sqlite3_int64 min_count = sqlite3_column_int64(statement, 4);
|
|
double min_ratio = sqlite3_column_double(statement, 5);
|
|
sqlite3_int64 seed_version = sqlite3_column_int64(statement, 6);
|
|
sqlite3_int64 canonical_version = sqlite3_column_int64(statement, 7);
|
|
if (code == SQLITE_DONE) {
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
} else if (code != SQLITE_ROW || after < 0 || !isfinite(threshold) || threshold <= 0.0 ||
|
|
!isfinite(confidence) || confidence <= 0.0 || confidence >= 1.0 ||
|
|
iterations <= 0 || iterations > INT_MAX || min_count <= 0 ||
|
|
min_count > 8192 || !isfinite(min_ratio) || min_ratio < 0.0 ||
|
|
min_ratio > 1.0 || seed_version <= 0 || seed_version > UINT32_MAX ||
|
|
canonical_version <= 0 || canonical_version > UINT32_MAX ||
|
|
sqlite3_column_bytes(statement, 8) != LARDON3D_PROJECT_DB_SHA256_SIZE) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
} else {
|
|
parameters->task_id = task_id;
|
|
parameters->after_match_result_id = (uint64_t)after;
|
|
parameters->threshold_pixels = threshold;
|
|
parameters->confidence = confidence;
|
|
parameters->max_iterations = (uint32_t)iterations;
|
|
parameters->min_inlier_count = (uint32_t)min_count;
|
|
parameters->min_inlier_ratio = min_ratio;
|
|
parameters->seed_policy_version = (uint32_t)seed_version;
|
|
parameters->canonicalization_version = (uint32_t)canonical_version;
|
|
memcpy(parameters->parameter_fingerprint, sqlite3_column_blob(statement, 8),
|
|
LARDON3D_PROJECT_DB_SHA256_SIZE);
|
|
}
|
|
sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
static bool read_match_result(sqlite3_stmt *statement,
|
|
Lardon3DProjectDbMatchResult *result) {
|
|
sqlite3_int64 id = sqlite3_column_int64(statement, 0);
|
|
sqlite3_int64 cp_id = sqlite3_column_int64(statement, 1);
|
|
sqlite3_int64 fs_a = sqlite3_column_int64(statement, 2);
|
|
sqlite3_int64 fs_b = sqlite3_column_int64(statement, 3);
|
|
const char *kind = (const char *)sqlite3_column_text(statement, 4);
|
|
sqlite3_int64 version = sqlite3_column_int64(statement, 5);
|
|
const void *fp = sqlite3_column_blob(statement, 6);
|
|
sqlite3_int64 status = sqlite3_column_int64(statement, 7);
|
|
sqlite3_int64 match_count = sqlite3_column_int64(statement, 8);
|
|
const void *asset_sha256 = sqlite3_column_blob(statement, 9);
|
|
int asset_sha256_bytes = sqlite3_column_bytes(statement, 9);
|
|
const char *asset_path = (const char *)sqlite3_column_text(statement, 10);
|
|
sqlite3_int64 asset_size = sqlite3_column_int64(statement, 11);
|
|
sqlite3_int64 created = sqlite3_column_int64(statement, 12);
|
|
bool has_sha = sqlite3_column_type(statement, 9) != SQLITE_NULL;
|
|
bool has_path = sqlite3_column_type(statement, 10) != SQLITE_NULL;
|
|
bool has_size = sqlite3_column_type(statement, 11) != SQLITE_NULL;
|
|
if (id <= 0 || cp_id <= 0 || fs_a <= 0 || fs_b <= 0 ||
|
|
!kind || version <= 0 || !fp || sqlite3_column_bytes(statement, 6) != 32 ||
|
|
status < 0 || status > 1 || match_count < 0 || match_count > 8192 || created < 0 ||
|
|
(status == LARDON3D_MATCH_RESULT_STATUS_NO_MATCH &&
|
|
(match_count != 0 || has_sha || has_path || has_size)) ||
|
|
(status == LARDON3D_MATCH_RESULT_STATUS_MATCHED &&
|
|
(match_count == 0 || !has_sha || !has_path || !has_size || asset_size <= 0))) {
|
|
return false;
|
|
}
|
|
result->match_result_id = (uint64_t)id;
|
|
result->candidate_pair_id = (uint64_t)cp_id;
|
|
result->feature_set_id_a = (uint64_t)fs_a;
|
|
result->feature_set_id_b = (uint64_t)fs_b;
|
|
size_t len = strlen(kind);
|
|
if (len == 0 || len >= LARDON3D_PROJECT_DB_KIND_CAPACITY) return false;
|
|
memcpy(result->matcher_kind, kind, len + 1);
|
|
result->matcher_version = (uint32_t)version;
|
|
memcpy(result->parameter_fingerprint, fp, 32);
|
|
result->result_status = (int)status;
|
|
result->match_count = (uint32_t)match_count;
|
|
/* Asset fields */
|
|
if (asset_sha256 && asset_sha256_bytes == LARDON3D_PROJECT_DB_SHA256_SIZE) {
|
|
result->has_match_asset = true;
|
|
memcpy(result->match_asset_sha256, asset_sha256, LARDON3D_PROJECT_DB_SHA256_SIZE);
|
|
} else {
|
|
result->has_match_asset = false;
|
|
memset(result->match_asset_sha256, 0, LARDON3D_PROJECT_DB_SHA256_SIZE);
|
|
}
|
|
if (asset_path) {
|
|
size_t plen = strlen(asset_path);
|
|
if (plen < LARDON3D_PROJECT_DB_PATH_CAPACITY) {
|
|
memcpy(result->match_asset_path, asset_path, plen + 1);
|
|
} else {
|
|
memcpy(result->match_asset_path, asset_path, LARDON3D_PROJECT_DB_PATH_CAPACITY - 1);
|
|
result->match_asset_path[LARDON3D_PROJECT_DB_PATH_CAPACITY - 1] = '\0';
|
|
}
|
|
} else {
|
|
result->match_asset_path[0] = '\0';
|
|
}
|
|
result->match_asset_size_bytes = asset_size >= 0 ? (uint64_t)asset_size : 0;
|
|
result->created_at = created;
|
|
return true;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_create_match_result(
|
|
Lardon3DProjectDb *database, uint64_t candidate_pair_id, uint64_t feature_set_id_a,
|
|
uint64_t feature_set_id_b, const char *matcher_kind, uint32_t matcher_version,
|
|
const unsigned char parameter_fingerprint[LARDON3D_PROJECT_DB_SHA256_SIZE], int result_status,
|
|
uint32_t match_count,
|
|
const unsigned char *match_asset_sha256, const char *match_asset_path,
|
|
uint64_t match_asset_size_bytes, int64_t created_at,
|
|
Lardon3DProjectDbMatchResult *result) {
|
|
if (!database || !valid_catalog_id(candidate_pair_id) ||
|
|
!valid_catalog_id(feature_set_id_a) || !valid_catalog_id(feature_set_id_b) ||
|
|
!bounded_text(matcher_kind, LARDON3D_PROJECT_DB_KIND_CAPACITY, false) ||
|
|
matcher_version == 0 || !parameter_fingerprint || result_status < 0 || result_status > 1 ||
|
|
match_count > 8192 || created_at < 0 || !result ||
|
|
(result_status == LARDON3D_MATCH_RESULT_STATUS_NO_MATCH &&
|
|
(match_count != 0 || match_asset_sha256 || match_asset_path || match_asset_size_bytes != 0)) ||
|
|
(result_status == LARDON3D_MATCH_RESULT_STATUS_MATCHED &&
|
|
(match_count == 0 || !match_asset_sha256 || !match_asset_path ||
|
|
match_asset_size_bytes == 0))) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
/* Validate asset path if provided */
|
|
if (match_asset_path && match_asset_path[0] != '\0' &&
|
|
!bounded_text(match_asset_path, LARDON3D_PROJECT_DB_PATH_CAPACITY, false)) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
memset(result, 0, sizeof(*result));
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
|
|
/* Validate Feature Set → image ownership before INSERT */
|
|
sqlite3_stmt *check = NULL;
|
|
Lardon3DProjectDbResult db_result = LARDON3D_PROJECT_DB_OK;
|
|
sqlite3_int64 cp_image_a = 0, cp_image_b = 0;
|
|
sqlite3_int64 fs_a_image = 0, fs_b_image = 0;
|
|
|
|
/* Load candidate pair image_ids */
|
|
db_result = prepare(database,
|
|
"SELECT image_id_a,image_id_b FROM candidate_pairs WHERE "
|
|
"candidate_pair_id=?1",
|
|
&check);
|
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(check, 1, (sqlite3_int64)candidate_pair_id);
|
|
int code = sqlite3_step(check);
|
|
if (code == SQLITE_DONE) {
|
|
db_result = LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
} else if (code != SQLITE_ROW) {
|
|
db_result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
} else {
|
|
cp_image_a = sqlite3_column_int64(check, 0);
|
|
cp_image_b = sqlite3_column_int64(check, 1);
|
|
}
|
|
(void)sqlite3_finalize(check);
|
|
check = NULL;
|
|
}
|
|
|
|
/* Verify feature_set_id_a belongs to image_id_a */
|
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
|
db_result = prepare(database,
|
|
"SELECT image_id FROM feature_sets WHERE feature_set_id=?1", &check);
|
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(check, 1, (sqlite3_int64)feature_set_id_a);
|
|
int code = sqlite3_step(check);
|
|
if (code == SQLITE_DONE) {
|
|
db_result = LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
} else if (code != SQLITE_ROW) {
|
|
db_result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
} else {
|
|
fs_a_image = sqlite3_column_int64(check, 0);
|
|
if (fs_a_image != cp_image_a) {
|
|
db_result = LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
}
|
|
(void)sqlite3_finalize(check);
|
|
check = NULL;
|
|
}
|
|
}
|
|
|
|
/* Verify feature_set_id_b belongs to image_id_b */
|
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
|
db_result = prepare(database,
|
|
"SELECT image_id FROM feature_sets WHERE feature_set_id=?1", &check);
|
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(check, 1, (sqlite3_int64)feature_set_id_b);
|
|
int code = sqlite3_step(check);
|
|
if (code == SQLITE_DONE) {
|
|
db_result = LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
} else if (code != SQLITE_ROW) {
|
|
db_result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
} else {
|
|
fs_b_image = sqlite3_column_int64(check, 0);
|
|
if (fs_b_image != cp_image_b) {
|
|
db_result = LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
}
|
|
(void)sqlite3_finalize(check);
|
|
check = NULL;
|
|
}
|
|
}
|
|
|
|
/* INSERT */
|
|
sqlite3_stmt *statement = NULL;
|
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
|
db_result = prepare(
|
|
database,
|
|
"INSERT INTO "
|
|
"match_results(candidate_pair_id,feature_set_id_a,feature_set_id_b,matcher_kind,"
|
|
"matcher_version,parameter_fingerprint,result_status,match_count,"
|
|
"match_asset_sha256,match_asset_path,match_asset_size_bytes,"
|
|
"created_at) VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12)",
|
|
&statement);
|
|
}
|
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)candidate_pair_id);
|
|
(void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)feature_set_id_a);
|
|
(void)sqlite3_bind_int64(statement, 3, (sqlite3_int64)feature_set_id_b);
|
|
(void)sqlite3_bind_text(statement, 4, matcher_kind, -1, SQLITE_TRANSIENT);
|
|
(void)sqlite3_bind_int64(statement, 5, matcher_version);
|
|
(void)sqlite3_bind_blob(statement, 6, parameter_fingerprint, 32, SQLITE_TRANSIENT);
|
|
(void)sqlite3_bind_int(statement, 7, result_status);
|
|
(void)sqlite3_bind_int64(statement, 8, match_count);
|
|
if (match_asset_sha256) {
|
|
(void)sqlite3_bind_blob(statement, 9, match_asset_sha256,
|
|
LARDON3D_PROJECT_DB_SHA256_SIZE, SQLITE_TRANSIENT);
|
|
} else {
|
|
(void)sqlite3_bind_null(statement, 9);
|
|
}
|
|
if (match_asset_path && match_asset_path[0] != '\0') {
|
|
(void)sqlite3_bind_text(statement, 10, match_asset_path, -1, SQLITE_TRANSIENT);
|
|
} else {
|
|
(void)sqlite3_bind_null(statement, 10);
|
|
}
|
|
if (result_status == LARDON3D_MATCH_RESULT_STATUS_MATCHED) {
|
|
(void)sqlite3_bind_int64(statement, 11, (sqlite3_int64)match_asset_size_bytes);
|
|
} else {
|
|
(void)sqlite3_bind_null(statement, 11);
|
|
}
|
|
(void)sqlite3_bind_int64(statement, 12, created_at);
|
|
db_result = step_done(database, statement, "create match result");
|
|
}
|
|
sqlite3_int64 id = sqlite3_last_insert_rowid(database->connection);
|
|
if (db_result == LARDON3D_PROJECT_DB_OK && id <= 0) {
|
|
db_result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
}
|
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
|
result->match_result_id = (uint64_t)id;
|
|
result->candidate_pair_id = candidate_pair_id;
|
|
result->feature_set_id_a = feature_set_id_a;
|
|
result->feature_set_id_b = feature_set_id_b;
|
|
(void)snprintf(result->matcher_kind, sizeof(result->matcher_kind), "%s", matcher_kind);
|
|
result->matcher_version = matcher_version;
|
|
memcpy(result->parameter_fingerprint, parameter_fingerprint, 32);
|
|
result->result_status = result_status;
|
|
result->match_count = match_count;
|
|
if (match_asset_sha256) {
|
|
result->has_match_asset = true;
|
|
memcpy(result->match_asset_sha256, match_asset_sha256, LARDON3D_PROJECT_DB_SHA256_SIZE);
|
|
} else {
|
|
result->has_match_asset = false;
|
|
memset(result->match_asset_sha256, 0, LARDON3D_PROJECT_DB_SHA256_SIZE);
|
|
}
|
|
if (match_asset_path && match_asset_path[0] != '\0') {
|
|
size_t plen = strlen(match_asset_path);
|
|
if (plen < LARDON3D_PROJECT_DB_PATH_CAPACITY) {
|
|
memcpy(result->match_asset_path, match_asset_path, plen + 1);
|
|
} else {
|
|
memcpy(result->match_asset_path, match_asset_path,
|
|
LARDON3D_PROJECT_DB_PATH_CAPACITY - 1);
|
|
result->match_asset_path[LARDON3D_PROJECT_DB_PATH_CAPACITY - 1] = '\0';
|
|
}
|
|
} else {
|
|
result->match_asset_path[0] = '\0';
|
|
}
|
|
result->match_asset_size_bytes = match_asset_size_bytes;
|
|
result->created_at = created_at;
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return db_result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_load_match_result(
|
|
Lardon3DProjectDb *database, uint64_t match_result_id,
|
|
Lardon3DProjectDbMatchResult *result) {
|
|
if (!database || !valid_catalog_id(match_result_id) || !result) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
memset(result, 0, sizeof(*result));
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult db_result = prepare(
|
|
database,
|
|
"SELECT "
|
|
"match_result_id,candidate_pair_id,feature_set_id_a,feature_set_id_b,matcher_kind,"
|
|
"matcher_version,parameter_fingerprint,result_status,match_count,"
|
|
"match_asset_sha256,match_asset_path,match_asset_size_bytes,"
|
|
"created_at FROM match_results WHERE match_result_id=?1",
|
|
&statement);
|
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)match_result_id);
|
|
int code = sqlite3_step(statement);
|
|
if (code == SQLITE_DONE) {
|
|
db_result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
} else if (code != SQLITE_ROW || !read_match_result(statement, result)) {
|
|
db_result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return db_result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_repair_match_result(
|
|
Lardon3DProjectDb *database, uint64_t match_result_id, int result_status,
|
|
uint32_t match_count, const unsigned char *match_asset_sha256,
|
|
const char *match_asset_path, uint64_t match_asset_size_bytes,
|
|
Lardon3DProjectDbMatchResult *result) {
|
|
if (!database || !valid_catalog_id(match_result_id) || result_status < 0 ||
|
|
result_status > 1 || match_count > 8192 || !result ||
|
|
(result_status == LARDON3D_MATCH_RESULT_STATUS_NO_MATCH &&
|
|
(match_count != 0 || match_asset_sha256 || match_asset_path || match_asset_size_bytes != 0)) ||
|
|
(result_status == LARDON3D_MATCH_RESULT_STATUS_MATCHED &&
|
|
(match_count == 0 || !match_asset_sha256 || !match_asset_path ||
|
|
match_asset_size_bytes == 0)) ||
|
|
(match_asset_path &&
|
|
!bounded_text(match_asset_path, LARDON3D_PROJECT_DB_PATH_CAPACITY, false))) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
memset(result, 0, sizeof(*result));
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult db_result = prepare(
|
|
database,
|
|
"UPDATE match_results SET result_status=?2,match_count=?3,match_asset_sha256=?4,"
|
|
"match_asset_path=?5,match_asset_size_bytes=?6 WHERE match_result_id=?1",
|
|
&statement);
|
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)match_result_id);
|
|
(void)sqlite3_bind_int(statement, 2, result_status);
|
|
(void)sqlite3_bind_int64(statement, 3, match_count);
|
|
if (match_asset_sha256) {
|
|
(void)sqlite3_bind_blob(statement, 4, match_asset_sha256, 32, SQLITE_TRANSIENT);
|
|
(void)sqlite3_bind_text(statement, 5, match_asset_path, -1, SQLITE_TRANSIENT);
|
|
(void)sqlite3_bind_int64(statement, 6, (sqlite3_int64)match_asset_size_bytes);
|
|
} else {
|
|
(void)sqlite3_bind_null(statement, 4);
|
|
(void)sqlite3_bind_null(statement, 5);
|
|
(void)sqlite3_bind_null(statement, 6);
|
|
}
|
|
db_result = step_done(database, statement, "repair match result");
|
|
}
|
|
if (db_result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1) {
|
|
db_result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
if (db_result != LARDON3D_PROJECT_DB_OK) return db_result;
|
|
return lardon3d_project_db_load_match_result(database, match_result_id, result);
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_find_match_result(
|
|
Lardon3DProjectDb *database, uint64_t candidate_pair_id,
|
|
uint64_t feature_set_id_a, uint64_t feature_set_id_b,
|
|
const char *matcher_kind, uint32_t matcher_version,
|
|
const unsigned char parameter_fingerprint[LARDON3D_PROJECT_DB_SHA256_SIZE],
|
|
Lardon3DProjectDbMatchResult *result) {
|
|
if (!database || !valid_catalog_id(candidate_pair_id) ||
|
|
!valid_catalog_id(feature_set_id_a) || !valid_catalog_id(feature_set_id_b) ||
|
|
!bounded_text(matcher_kind, LARDON3D_PROJECT_DB_KIND_CAPACITY, false) ||
|
|
matcher_version == 0 || !parameter_fingerprint || !result) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
memset(result, 0, sizeof(*result));
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult db_result = prepare(
|
|
database,
|
|
"SELECT "
|
|
"match_result_id,candidate_pair_id,feature_set_id_a,feature_set_id_b,matcher_kind,"
|
|
"matcher_version,parameter_fingerprint,result_status,match_count,"
|
|
"match_asset_sha256,match_asset_path,match_asset_size_bytes,"
|
|
"created_at FROM match_results WHERE candidate_pair_id=?1 AND feature_set_id_a=?2 AND "
|
|
"feature_set_id_b=?3 AND matcher_kind=?4 AND matcher_version=?5 AND "
|
|
"parameter_fingerprint=?6",
|
|
&statement);
|
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)candidate_pair_id);
|
|
(void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)feature_set_id_a);
|
|
(void)sqlite3_bind_int64(statement, 3, (sqlite3_int64)feature_set_id_b);
|
|
(void)sqlite3_bind_text(statement, 4, matcher_kind, -1, SQLITE_TRANSIENT);
|
|
(void)sqlite3_bind_int64(statement, 5, matcher_version);
|
|
(void)sqlite3_bind_blob(statement, 6, parameter_fingerprint, 32, SQLITE_TRANSIENT);
|
|
int code = sqlite3_step(statement);
|
|
if (code == SQLITE_DONE) {
|
|
db_result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
} else if (code != SQLITE_ROW || !read_match_result(statement, result)) {
|
|
db_result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return db_result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_list_match_results(
|
|
Lardon3DProjectDb *database, uint64_t after_match_result_id,
|
|
Lardon3DProjectDbMatchResult *results, size_t capacity, size_t *count) {
|
|
if (count) {
|
|
*count = 0;
|
|
}
|
|
if (!database || !results || !count || after_match_result_id > INT64_MAX || capacity == 0 ||
|
|
capacity > LARDON3D_PROJECT_DB_MATCH_RESULT_PAGE_MAX) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult db_result = prepare(
|
|
database,
|
|
"SELECT "
|
|
"match_result_id,candidate_pair_id,feature_set_id_a,feature_set_id_b,matcher_kind,"
|
|
"matcher_version,parameter_fingerprint,result_status,match_count,"
|
|
"match_asset_sha256,match_asset_path,match_asset_size_bytes,"
|
|
"created_at FROM match_results WHERE match_result_id>?1 ORDER BY match_result_id LIMIT ?2",
|
|
&statement);
|
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)after_match_result_id);
|
|
(void)sqlite3_bind_int64(statement, 2, (sqlite3_int64)capacity);
|
|
int code = SQLITE_DONE;
|
|
while (*count < capacity && (code = sqlite3_step(statement)) == SQLITE_ROW) {
|
|
if (!read_match_result(statement, &results[*count])) {
|
|
db_result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
break;
|
|
}
|
|
++*count;
|
|
}
|
|
if (db_result == LARDON3D_PROJECT_DB_OK && *count < capacity && code != SQLITE_DONE) {
|
|
db_result = sqlite_result(database, code, "list match results");
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return db_result;
|
|
}
|
|
|
|
static size_t geometric_mask_size(uint32_t match_count) {
|
|
return ((size_t)match_count + 7U) / 8U;
|
|
}
|
|
|
|
static uint32_t geometric_mask_popcount(const unsigned char *mask, size_t size) {
|
|
uint32_t count = 0;
|
|
for (size_t byte_index = 0; byte_index < size; ++byte_index) {
|
|
unsigned int value = mask[byte_index];
|
|
while (value != 0U) {
|
|
value &= value - 1U;
|
|
++count;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static bool valid_geometric_mask(const unsigned char *mask, size_t size, uint32_t match_count,
|
|
uint32_t inlier_count) {
|
|
size_t expected_size = geometric_mask_size(match_count);
|
|
if (!mask || size != expected_size || expected_size == 0 ||
|
|
expected_size > LARDON3D_PROJECT_DB_INLIER_MASK_MAX || inlier_count > match_count) {
|
|
return false;
|
|
}
|
|
unsigned int used_bits = match_count % 8U;
|
|
if (used_bits != 0U) {
|
|
unsigned int padding_mask = 0xffU << used_bits;
|
|
if (((unsigned int)mask[size - 1] & padding_mask) != 0U) {
|
|
return false;
|
|
}
|
|
}
|
|
return geometric_mask_popcount(mask, size) == inlier_count;
|
|
}
|
|
|
|
static bool valid_geometric_model(Lardon3DGeometricVerificationStatus status,
|
|
const double *model) {
|
|
if (status == LARDON3D_GEOMETRIC_REJECTED) {
|
|
return model == NULL;
|
|
}
|
|
if (status != LARDON3D_GEOMETRIC_VERIFIED || !model) {
|
|
return false;
|
|
}
|
|
for (size_t index = 0; index < LARDON3D_PROJECT_DB_FUNDAMENTAL_COEFFICIENTS; ++index) {
|
|
if (!isfinite(model[index])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static const char geometric_result_columns[] =
|
|
"g.geometric_verification_result_id,g.match_result_id,g.verifier_kind,"
|
|
"g.verifier_version,g.parameter_fingerprint,g.status,g.inlier_count,g.inlier_mask,"
|
|
"g.model_m00,g.model_m01,g.model_m02,g.model_m10,g.model_m11,g.model_m12,"
|
|
"g.model_m20,g.model_m21,g.model_m22,g.created_at,m.result_status,m.match_count ";
|
|
|
|
static bool read_geometric_result(sqlite3_stmt *statement,
|
|
Lardon3DProjectDbGeometricVerificationResult *result) {
|
|
if (!statement || !result) {
|
|
return false;
|
|
}
|
|
for (int column = 0; column <= 7; ++column) {
|
|
int expected_type = column == 4 || column == 7 ? SQLITE_BLOB : SQLITE_INTEGER;
|
|
if (sqlite3_column_type(statement, column) != expected_type) {
|
|
return false;
|
|
}
|
|
}
|
|
if (sqlite3_column_type(statement, 17) != SQLITE_INTEGER ||
|
|
sqlite3_column_type(statement, 18) != SQLITE_INTEGER ||
|
|
sqlite3_column_type(statement, 19) != SQLITE_INTEGER) {
|
|
return false;
|
|
}
|
|
sqlite3_int64 id = sqlite3_column_int64(statement, 0);
|
|
sqlite3_int64 parent_id = sqlite3_column_int64(statement, 1);
|
|
sqlite3_int64 kind = sqlite3_column_int64(statement, 2);
|
|
sqlite3_int64 version = sqlite3_column_int64(statement, 3);
|
|
sqlite3_int64 status = sqlite3_column_int64(statement, 5);
|
|
sqlite3_int64 inlier_count = sqlite3_column_int64(statement, 6);
|
|
sqlite3_int64 created_at = sqlite3_column_int64(statement, 17);
|
|
sqlite3_int64 parent_status = sqlite3_column_int64(statement, 18);
|
|
sqlite3_int64 match_count = sqlite3_column_int64(statement, 19);
|
|
int fingerprint_size = sqlite3_column_bytes(statement, 4);
|
|
int mask_size = sqlite3_column_bytes(statement, 7);
|
|
const void *fingerprint = sqlite3_column_blob(statement, 4);
|
|
const void *mask = sqlite3_column_blob(statement, 7);
|
|
if (id <= 0 || parent_id <= 0 || kind != LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL ||
|
|
version <= 0 || version > UINT32_MAX ||
|
|
(status != LARDON3D_GEOMETRIC_REJECTED && status != LARDON3D_GEOMETRIC_VERIFIED) ||
|
|
inlier_count < 0 || inlier_count > 8192 || created_at < 0 ||
|
|
parent_status != LARDON3D_MATCH_RESULT_STATUS_MATCHED || match_count <= 0 ||
|
|
match_count > 8192 || fingerprint_size != LARDON3D_PROJECT_DB_SHA256_SIZE ||
|
|
!fingerprint || mask_size <= 0 || mask_size > LARDON3D_PROJECT_DB_INLIER_MASK_MAX || !mask ||
|
|
!valid_geometric_mask(mask, (size_t)mask_size, (uint32_t)match_count,
|
|
(uint32_t)inlier_count)) {
|
|
return false;
|
|
}
|
|
double model[LARDON3D_PROJECT_DB_FUNDAMENTAL_COEFFICIENTS] = {0};
|
|
bool has_model = status == LARDON3D_GEOMETRIC_VERIFIED;
|
|
for (int index = 0; index < LARDON3D_PROJECT_DB_FUNDAMENTAL_COEFFICIENTS; ++index) {
|
|
int type = sqlite3_column_type(statement, 8 + index);
|
|
if ((!has_model && type != SQLITE_NULL) ||
|
|
(has_model && type != SQLITE_FLOAT && type != SQLITE_INTEGER)) {
|
|
return false;
|
|
}
|
|
if (has_model) {
|
|
model[index] = sqlite3_column_double(statement, 8 + index);
|
|
if (!isfinite(model[index])) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
memset(result, 0, sizeof(*result));
|
|
result->geometric_verification_result_id = (uint64_t)id;
|
|
result->match_result_id = (uint64_t)parent_id;
|
|
result->verifier_kind = (Lardon3DGeometricVerifierKind)kind;
|
|
result->verifier_version = (uint32_t)version;
|
|
memcpy(result->parameter_fingerprint, fingerprint, LARDON3D_PROJECT_DB_SHA256_SIZE);
|
|
result->status = (Lardon3DGeometricVerificationStatus)status;
|
|
result->inlier_count = (uint32_t)inlier_count;
|
|
result->inlier_mask_size = (size_t)mask_size;
|
|
memcpy(result->inlier_mask, mask, (size_t)mask_size);
|
|
result->has_model = has_model;
|
|
memcpy(result->model, model, sizeof(model));
|
|
result->created_at = created_at;
|
|
return true;
|
|
}
|
|
|
|
static Lardon3DProjectDbResult read_single_geometric_result(
|
|
Lardon3DProjectDb *database, sqlite3_stmt *statement,
|
|
Lardon3DProjectDbGeometricVerificationResult *result) {
|
|
int code = sqlite3_step(statement);
|
|
if (code == SQLITE_DONE) {
|
|
(void)sqlite3_finalize(statement);
|
|
return LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
}
|
|
if (code != SQLITE_ROW) {
|
|
Lardon3DProjectDbResult db_result = sqlite_result(database, code, "read geometric result");
|
|
(void)sqlite3_finalize(statement);
|
|
return db_result;
|
|
}
|
|
bool valid = read_geometric_result(statement, result);
|
|
code = sqlite3_step(statement);
|
|
(void)sqlite3_finalize(statement);
|
|
return valid && code == SQLITE_DONE ? LARDON3D_PROJECT_DB_OK : LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_create_geometric_verification_result(
|
|
Lardon3DProjectDb *database, uint64_t match_result_id,
|
|
Lardon3DGeometricVerifierKind verifier_kind, uint32_t verifier_version,
|
|
const unsigned char parameter_fingerprint[LARDON3D_PROJECT_DB_SHA256_SIZE],
|
|
Lardon3DGeometricVerificationStatus status, uint32_t inlier_count,
|
|
const unsigned char *inlier_mask, size_t inlier_mask_size, const double *model,
|
|
int64_t created_at, Lardon3DProjectDbGeometricVerificationResult *result) {
|
|
if (!database || !valid_catalog_id(match_result_id) ||
|
|
verifier_kind != LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL || verifier_version == 0 ||
|
|
!parameter_fingerprint || inlier_count > 8192 || !inlier_mask || inlier_mask_size == 0 ||
|
|
inlier_mask_size > LARDON3D_PROJECT_DB_INLIER_MASK_MAX ||
|
|
!valid_geometric_model(status, model) || created_at < 0 || !result) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
memset(result, 0, sizeof(*result));
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
Lardon3DProjectDbResult db_result =
|
|
execute(database, "BEGIN IMMEDIATE", "begin geometric result creation");
|
|
sqlite3_stmt *statement = NULL;
|
|
uint32_t match_count = 0;
|
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
|
db_result = prepare(database,
|
|
"SELECT result_status,match_count FROM match_results "
|
|
"WHERE match_result_id=?1",
|
|
&statement);
|
|
}
|
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)match_result_id);
|
|
int code = sqlite3_step(statement);
|
|
if (code == SQLITE_DONE) {
|
|
db_result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
} else if (code != SQLITE_ROW || sqlite3_column_type(statement, 0) != SQLITE_INTEGER ||
|
|
sqlite3_column_type(statement, 1) != SQLITE_INTEGER) {
|
|
db_result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
} else {
|
|
sqlite3_int64 parent_status = sqlite3_column_int64(statement, 0);
|
|
sqlite3_int64 parent_count = sqlite3_column_int64(statement, 1);
|
|
if (parent_status != LARDON3D_MATCH_RESULT_STATUS_MATCHED || parent_count <= 0 ||
|
|
parent_count > 8192) {
|
|
db_result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
} else {
|
|
match_count = (uint32_t)parent_count;
|
|
}
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
statement = NULL;
|
|
}
|
|
if (db_result == LARDON3D_PROJECT_DB_OK &&
|
|
!valid_geometric_mask(inlier_mask, inlier_mask_size, match_count, inlier_count)) {
|
|
db_result = LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
|
db_result = prepare(
|
|
database,
|
|
"INSERT INTO geometric_verification_results("
|
|
"match_result_id,verifier_kind,verifier_version,parameter_fingerprint,status,"
|
|
"inlier_count,inlier_mask,model_m00,model_m01,model_m02,model_m10,model_m11,model_m12,"
|
|
"model_m20,model_m21,model_m22,created_at) "
|
|
"VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15,?16,?17)",
|
|
&statement);
|
|
}
|
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)match_result_id);
|
|
(void)sqlite3_bind_int(statement, 2, (int)verifier_kind);
|
|
(void)sqlite3_bind_int64(statement, 3, (sqlite3_int64)verifier_version);
|
|
(void)sqlite3_bind_blob(statement, 4, parameter_fingerprint,
|
|
LARDON3D_PROJECT_DB_SHA256_SIZE, SQLITE_TRANSIENT);
|
|
(void)sqlite3_bind_int(statement, 5, (int)status);
|
|
(void)sqlite3_bind_int64(statement, 6, (sqlite3_int64)inlier_count);
|
|
(void)sqlite3_bind_blob(statement, 7, inlier_mask, (int)inlier_mask_size, SQLITE_TRANSIENT);
|
|
for (int index = 0; index < LARDON3D_PROJECT_DB_FUNDAMENTAL_COEFFICIENTS; ++index) {
|
|
if (model) {
|
|
(void)sqlite3_bind_double(statement, 8 + index, model[index]);
|
|
} else {
|
|
(void)sqlite3_bind_null(statement, 8 + index);
|
|
}
|
|
}
|
|
(void)sqlite3_bind_int64(statement, 17, created_at);
|
|
db_result = step_done(database, statement, "create geometric result");
|
|
statement = NULL;
|
|
}
|
|
uint64_t result_id = 0;
|
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_int64 inserted_id = sqlite3_last_insert_rowid(database->connection);
|
|
if (inserted_id <= 0) {
|
|
db_result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
} else {
|
|
result_id = (uint64_t)inserted_id;
|
|
db_result = execute(database, "COMMIT", "commit geometric result creation");
|
|
}
|
|
}
|
|
if (db_result != LARDON3D_PROJECT_DB_OK) {
|
|
(void)execute(database, "ROLLBACK", "rollback geometric result creation");
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
if (db_result != LARDON3D_PROJECT_DB_OK) {
|
|
return db_result;
|
|
}
|
|
result->geometric_verification_result_id = result_id;
|
|
result->match_result_id = match_result_id;
|
|
result->verifier_kind = verifier_kind;
|
|
result->verifier_version = verifier_version;
|
|
memcpy(result->parameter_fingerprint, parameter_fingerprint,
|
|
LARDON3D_PROJECT_DB_SHA256_SIZE);
|
|
result->status = status;
|
|
result->inlier_count = inlier_count;
|
|
result->inlier_mask_size = inlier_mask_size;
|
|
memcpy(result->inlier_mask, inlier_mask, inlier_mask_size);
|
|
result->has_model = model != NULL;
|
|
if (model) {
|
|
memcpy(result->model, model, sizeof(result->model));
|
|
}
|
|
result->created_at = created_at;
|
|
return LARDON3D_PROJECT_DB_OK;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_load_geometric_verification_result(
|
|
Lardon3DProjectDb *database, uint64_t geometric_verification_result_id,
|
|
Lardon3DProjectDbGeometricVerificationResult *result) {
|
|
if (!database || !valid_catalog_id(geometric_verification_result_id) || !result) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
memset(result, 0, sizeof(*result));
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
char sql[1024];
|
|
(void)snprintf(sql, sizeof(sql), "SELECT %sFROM geometric_verification_results g "
|
|
"JOIN match_results m ON m.match_result_id=g.match_result_id "
|
|
"WHERE g.geometric_verification_result_id=?1",
|
|
geometric_result_columns);
|
|
Lardon3DProjectDbResult db_result = prepare(database, sql, &statement);
|
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)geometric_verification_result_id);
|
|
db_result = read_single_geometric_result(database, statement, result);
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return db_result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_find_geometric_verification_result(
|
|
Lardon3DProjectDb *database, uint64_t match_result_id,
|
|
Lardon3DGeometricVerifierKind verifier_kind, uint32_t verifier_version,
|
|
const unsigned char parameter_fingerprint[LARDON3D_PROJECT_DB_SHA256_SIZE],
|
|
Lardon3DProjectDbGeometricVerificationResult *result) {
|
|
if (!database || !valid_catalog_id(match_result_id) ||
|
|
verifier_kind != LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL || verifier_version == 0 ||
|
|
!parameter_fingerprint || !result) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
memset(result, 0, sizeof(*result));
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
char sql[1280];
|
|
(void)snprintf(sql, sizeof(sql), "SELECT %sFROM geometric_verification_results g "
|
|
"JOIN match_results m ON m.match_result_id=g.match_result_id "
|
|
"WHERE g.match_result_id=?1 AND g.verifier_kind=?2 AND "
|
|
"g.verifier_version=?3 AND g.parameter_fingerprint=?4",
|
|
geometric_result_columns);
|
|
Lardon3DProjectDbResult db_result = prepare(database, sql, &statement);
|
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)match_result_id);
|
|
(void)sqlite3_bind_int(statement, 2, (int)verifier_kind);
|
|
(void)sqlite3_bind_int64(statement, 3, (sqlite3_int64)verifier_version);
|
|
(void)sqlite3_bind_blob(statement, 4, parameter_fingerprint,
|
|
LARDON3D_PROJECT_DB_SHA256_SIZE, SQLITE_TRANSIENT);
|
|
db_result = read_single_geometric_result(database, statement, result);
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return db_result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_list_geometric_verification_results(
|
|
Lardon3DProjectDb *database, uint64_t match_result_id,
|
|
uint64_t after_geometric_verification_result_id,
|
|
Lardon3DProjectDbGeometricVerificationResult *results, size_t capacity, size_t *count) {
|
|
if (count) {
|
|
*count = 0;
|
|
}
|
|
if (!database || !valid_catalog_id(match_result_id) ||
|
|
after_geometric_verification_result_id > INT64_MAX || !results || !count || capacity == 0 ||
|
|
capacity > LARDON3D_PROJECT_DB_GEOMETRIC_RESULT_PAGE_MAX) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
char sql[1280];
|
|
(void)snprintf(sql, sizeof(sql), "SELECT %sFROM geometric_verification_results g "
|
|
"JOIN match_results m ON m.match_result_id=g.match_result_id "
|
|
"WHERE g.match_result_id=?1 AND "
|
|
"g.geometric_verification_result_id>?2 "
|
|
"ORDER BY g.geometric_verification_result_id LIMIT ?3",
|
|
geometric_result_columns);
|
|
Lardon3DProjectDbResult db_result = prepare(database, sql, &statement);
|
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)match_result_id);
|
|
(void)sqlite3_bind_int64(statement, 2,
|
|
(sqlite3_int64)after_geometric_verification_result_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_geometric_result(statement, &results[*count])) {
|
|
db_result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
break;
|
|
}
|
|
++*count;
|
|
}
|
|
if (db_result == LARDON3D_PROJECT_DB_OK && *count < capacity && code != SQLITE_DONE) {
|
|
db_result = sqlite_result(database, code, "list geometric results");
|
|
}
|
|
(void)sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return db_result;
|
|
}
|
|
|
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
|
Lardon3DProjectDbResult lardon3d_project_db_test_delete_feature_identity(
|
|
Lardon3DProjectDb *database, uint64_t feature_set_id, uint64_t feature_asset_id) {
|
|
if (!database || !valid_catalog_id(feature_set_id) || !valid_catalog_id(feature_asset_id)) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
Lardon3DProjectDbResult result =
|
|
execute(database, "BEGIN IMMEDIATE", "begin delete feature identity");
|
|
sqlite3_stmt *s = NULL;
|
|
const char *sql[2] = {"DELETE FROM feature_sets WHERE feature_set_id=?1",
|
|
"DELETE FROM feature_assets WHERE feature_asset_id=?1"};
|
|
uint64_t ids[2] = {feature_set_id, feature_asset_id};
|
|
for (size_t i = 0; i < 2 && result == LARDON3D_PROJECT_DB_OK; ++i) {
|
|
result = prepare(database, sql[i], &s);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(s, 1, (sqlite3_int64)ids[i]);
|
|
result = step_done(database, s, "delete feature identity");
|
|
s = NULL;
|
|
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1) {
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
}
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = execute(database, "COMMIT", "commit delete feature identity");
|
|
}
|
|
if (result != LARDON3D_PROJECT_DB_OK) {
|
|
(void)execute(database, "ROLLBACK", "rollback delete feature identity");
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_project_db_test_orphan_checkpoint(Lardon3DProjectDb *database) {
|
|
if (!database) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result =
|
|
prepare(database,
|
|
"INSERT INTO checkpoints(task_id,path,format_version,durability,updated_at) "
|
|
"VALUES(9223372036854775807,'orphan',1,0,0)",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = step_done(database, statement, "orphan checkpoint");
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult
|
|
lardon3d_project_db_test_delete_catalog_identity(Lardon3DProjectDb *database, uint64_t scanset_id,
|
|
uint64_t image_id, uint64_t asset_id) {
|
|
if (!database || !valid_catalog_id(scanset_id) || !valid_catalog_id(image_id) ||
|
|
!valid_catalog_id(asset_id)) {
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
(void)pthread_mutex_lock(&database->mutex);
|
|
Lardon3DProjectDbResult result =
|
|
execute(database, "BEGIN IMMEDIATE", "begin catalog identity deletion test");
|
|
const char *sql[] = {
|
|
"DELETE FROM images WHERE image_id=?1",
|
|
"DELETE FROM image_assets WHERE asset_id=?1",
|
|
"DELETE FROM scansets WHERE scanset_id=?1",
|
|
};
|
|
const uint64_t ids[] = {image_id, asset_id, scanset_id};
|
|
for (size_t index = 0; index < 3 && result == LARDON3D_PROJECT_DB_OK; ++index) {
|
|
sqlite3_stmt *statement = NULL;
|
|
result = prepare(database, sql[index], &statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)ids[index]);
|
|
result = step_done(database, statement, "delete catalog identity test");
|
|
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1) {
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
}
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = execute(database, "COMMIT", "commit catalog identity deletion test");
|
|
}
|
|
if (result != LARDON3D_PROJECT_DB_OK) {
|
|
(void)execute(database, "ROLLBACK", "rollback catalog identity deletion test");
|
|
}
|
|
(void)pthread_mutex_unlock(&database->mutex);
|
|
return result;
|
|
}
|
|
#endif
|