Storage.vala 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. /**
  2. * High-level storage interface and implementation for Implexus.
  3. *
  4. * Provides entity persistence operations built on top of the Dbm layer.
  5. */
  6. namespace Implexus.Storage {
  7. /**
  8. * Configuration for a Category/Index entity.
  9. */
  10. public class CategoryConfig : Object {
  11. /**
  12. * The type label for entities in this category.
  13. */
  14. public string type_label { get; set; }
  15. /**
  16. * The expression used to generate the index.
  17. */
  18. public string expression { get; set; }
  19. /**
  20. * Creates a new CategoryConfig.
  21. */
  22. public CategoryConfig(string type_label, string expression) {
  23. this.type_label = type_label;
  24. this.expression = expression;
  25. }
  26. }
  27. /**
  28. * Configuration for a Catalogue entity.
  29. */
  30. public class CatalogueConfig : Object {
  31. /**
  32. * The type label for documents to catalogue.
  33. */
  34. public string type_label { get; set; }
  35. /**
  36. * The expression used to extract the grouping key.
  37. */
  38. public string expression { get; set; }
  39. /**
  40. * Creates a new CatalogueConfig.
  41. */
  42. public CatalogueConfig(string type_label, string expression) {
  43. this.type_label = type_label;
  44. this.expression = expression;
  45. }
  46. }
  47. /**
  48. * Interface for high-level entity storage operations.
  49. */
  50. public interface Storage : Object {
  51. /**
  52. * Stores entity metadata.
  53. *
  54. * @param path The entity path
  55. * @param type The entity type
  56. * @param type_label Optional type label for Categories
  57. */
  58. public abstract void store_entity_metadata(Core.EntityPath path, Core.EntityType type, string? type_label = null) throws StorageError;
  59. /**
  60. * Gets the entity type for a path.
  61. *
  62. * @param path The entity path
  63. * @return The entity type, or null if not found
  64. */
  65. public abstract Core.EntityType? get_entity_type(Core.EntityPath path) throws StorageError;
  66. /**
  67. * Gets the entity type label for a path.
  68. *
  69. * @param path The entity path
  70. * @return The type label, or null if not found
  71. */
  72. public abstract string? get_entity_type_label(Core.EntityPath path) throws StorageError;
  73. /**
  74. * Checks if an entity exists at the given path.
  75. *
  76. * @param path The entity path
  77. * @return True if the entity exists
  78. */
  79. public abstract bool entity_exists(Core.EntityPath path);
  80. /**
  81. * Deletes an entity and all its data.
  82. *
  83. * @param path The entity path
  84. */
  85. public abstract void delete_entity(Core.EntityPath path) throws StorageError;
  86. /**
  87. * Stores document properties for an entity.
  88. *
  89. * @param path The entity path
  90. * @param properties The properties to store
  91. */
  92. public abstract void store_properties(Core.EntityPath path, Invercargill.Properties properties) throws StorageError;
  93. /**
  94. * Loads document properties for an entity.
  95. *
  96. * @param path The entity path
  97. * @return The properties, or null if not found
  98. */
  99. public abstract Invercargill.Properties? load_properties(Core.EntityPath path) throws StorageError;
  100. /**
  101. * Adds a child name to a category.
  102. *
  103. * @param parent The parent category path
  104. * @param child_name The name of the child
  105. */
  106. public abstract void add_child(Core.EntityPath parent, string child_name) throws StorageError;
  107. /**
  108. * Removes a child name from a category.
  109. *
  110. * @param parent The parent category path
  111. * @param child_name The name of the child
  112. */
  113. public abstract void remove_child(Core.EntityPath parent, string child_name) throws StorageError;
  114. /**
  115. * Gets the children of a category.
  116. *
  117. * @param parent The parent category path
  118. * @return Enumerable of child names
  119. */
  120. public abstract Invercargill.Enumerable<string> get_children(Core.EntityPath parent) throws StorageError;
  121. /**
  122. * Checks if a category has a specific child.
  123. *
  124. * @param parent The parent category path
  125. * @param child_name The name of the child
  126. * @return True if the child exists
  127. */
  128. public abstract bool has_child(Core.EntityPath parent, string child_name) throws StorageError;
  129. /**
  130. * Stores category configuration.
  131. *
  132. * @param path The category path
  133. * @param type_label The type label for entities
  134. * @param expression The index expression
  135. */
  136. public abstract void store_category_config(Core.EntityPath path, string type_label, string expression) throws StorageError;
  137. /**
  138. * Gets category configuration.
  139. *
  140. * @param path The category path
  141. * @return The configuration, or null if not found
  142. */
  143. public abstract CategoryConfig? get_category_config(Core.EntityPath path) throws StorageError;
  144. /**
  145. * Stores catalogue configuration.
  146. *
  147. * @param path The catalogue path
  148. * @param type_label The type label for documents to catalogue
  149. * @param expression The expression to extract the grouping key
  150. */
  151. public abstract void store_catalogue_config(Core.EntityPath path, string type_label, string expression) throws StorageError;
  152. /**
  153. * Gets catalogue configuration.
  154. *
  155. * @param path The catalogue path
  156. * @return The configuration, or null if not found
  157. */
  158. public abstract CatalogueConfig? get_catalogue_config(Core.EntityPath path) throws StorageError;
  159. }
  160. /**
  161. * Basic implementation of Storage using Dbm.
  162. */
  163. public class BasicStorage : Object, Storage {
  164. /**
  165. * The underlying Dbm backend.
  166. *
  167. * This is exposed to allow IndexManager to share the same storage.
  168. */
  169. public Dbm dbm { get { return _dbm; } }
  170. private Dbm _dbm;
  171. // Key prefixes for different data types
  172. private const string ENTITY_PREFIX = "entity:";
  173. private const string PROPS_PREFIX = "props:";
  174. private const string CHILDREN_PREFIX = "children:";
  175. private const string CONFIG_PREFIX = "config:";
  176. /**
  177. * Creates a new BasicStorage with the given Dbm backend.
  178. *
  179. * @param dbm The Dbm backend to use
  180. */
  181. public BasicStorage(Dbm dbm) {
  182. _dbm = dbm;
  183. }
  184. /**
  185. * Creates a new BasicStorage with a file-based Dbm.
  186. *
  187. * @param data_dir Directory to store data in
  188. */
  189. public BasicStorage.with_directory(string data_dir) {
  190. _dbm = new FilesystemDbm(data_dir);
  191. }
  192. /**
  193. * {@inheritDoc}
  194. */
  195. public void store_entity_metadata(Core.EntityPath path, Core.EntityType type, string? type_label = null) throws StorageError {
  196. string key = ENTITY_PREFIX + path.to_string();
  197. var writer = new ElementWriter();
  198. // Use write_element to include type code, so read_element can read it back
  199. writer.write_element(new Invercargill.NativeElement<int64?>((int64) type));
  200. writer.write_element(new Invercargill.NativeElement<string>(type_label ?? ""));
  201. _dbm.set(key, writer.to_binary_data());
  202. }
  203. /**
  204. * {@inheritDoc}
  205. */
  206. public Core.EntityType? get_entity_type(Core.EntityPath path) throws StorageError {
  207. string key = ENTITY_PREFIX + path.to_string();
  208. var data = _dbm.get(key);
  209. if (data == null) {
  210. return null;
  211. }
  212. var reader = new ElementReader((!) data);
  213. try {
  214. var element = reader.read_element();
  215. if (element.is_null()) {
  216. return null;
  217. }
  218. int64? type_val = element.as<int64?>();
  219. return (Core.EntityType) (type_val == null ? 0 : (!) type_val);
  220. } catch (Invercargill.ElementError e) {
  221. throw new StorageError.CORRUPT_DATA("Failed to read entity type: %s".printf(e.message));
  222. }
  223. }
  224. /**
  225. * {@inheritDoc}
  226. */
  227. public string? get_entity_type_label(Core.EntityPath path) throws StorageError {
  228. string key = ENTITY_PREFIX + path.to_string();
  229. var data = _dbm.get(key);
  230. if (data == null) {
  231. return null;
  232. }
  233. var reader = new ElementReader((!) data);
  234. try {
  235. reader.read_element(); // Skip type
  236. var label_element = reader.read_element();
  237. if (label_element.is_null()) {
  238. return null;
  239. }
  240. string label = label_element.as<string>();
  241. return label == "" ? null : label;
  242. } catch (Invercargill.ElementError e) {
  243. throw new StorageError.CORRUPT_DATA("Failed to read entity type label: %s".printf(e.message));
  244. }
  245. }
  246. /**
  247. * {@inheritDoc}
  248. */
  249. public bool entity_exists(Core.EntityPath path) {
  250. string key = ENTITY_PREFIX + path.to_string();
  251. return _dbm.has_key(key);
  252. }
  253. /**
  254. * {@inheritDoc}
  255. */
  256. public void delete_entity(Core.EntityPath path) throws StorageError {
  257. string path_str = path.to_string();
  258. // Delete entity metadata
  259. _dbm.delete(ENTITY_PREFIX + path_str);
  260. // Delete properties
  261. _dbm.delete(PROPS_PREFIX + path_str);
  262. // Delete children (for categories)
  263. _dbm.delete(CHILDREN_PREFIX + path_str);
  264. // Delete category config (for categories)
  265. _dbm.delete(CONFIG_PREFIX + path_str);
  266. }
  267. /**
  268. * {@inheritDoc}
  269. */
  270. public void store_properties(Core.EntityPath path, Invercargill.Properties properties) throws StorageError {
  271. string key = PROPS_PREFIX + path.to_string();
  272. var writer = new ElementWriter();
  273. // Use write_element to include type code, so read_element can read it back
  274. writer.write_element(new Invercargill.NativeElement<Invercargill.Properties>(properties));
  275. _dbm.set(key, writer.to_binary_data());
  276. }
  277. /**
  278. * {@inheritDoc}
  279. */
  280. public Invercargill.Properties? load_properties(Core.EntityPath path) throws StorageError {
  281. string key = PROPS_PREFIX + path.to_string();
  282. var data = _dbm.get(key);
  283. if (data == null) {
  284. return null;
  285. }
  286. var reader = new ElementReader((!) data);
  287. try {
  288. var element = reader.read_element();
  289. if (element.is_null()) {
  290. return null;
  291. }
  292. return element.as<Invercargill.Properties>();
  293. } catch (Invercargill.ElementError e) {
  294. throw new StorageError.CORRUPT_DATA("Failed to read properties: %s".printf(e.message));
  295. }
  296. }
  297. /**
  298. * {@inheritDoc}
  299. */
  300. public void add_child(Core.EntityPath parent, string child_name) throws StorageError {
  301. var children = load_children_set(parent);
  302. try {
  303. children.set(child_name, true);
  304. } catch (Invercargill.IndexError e) {
  305. throw new StorageError.IO_ERROR("Failed to add child: %s".printf(e.message));
  306. }
  307. save_children_set(parent, children);
  308. }
  309. /**
  310. * {@inheritDoc}
  311. */
  312. public void remove_child(Core.EntityPath parent, string child_name) throws StorageError {
  313. var children = load_children_set(parent);
  314. try {
  315. children.remove(child_name);
  316. } catch (Invercargill.IndexError e) {
  317. // Child not in set, that's fine
  318. }
  319. save_children_set(parent, children);
  320. }
  321. /**
  322. * {@inheritDoc}
  323. */
  324. public Invercargill.Enumerable<string> get_children(Core.EntityPath parent) throws StorageError {
  325. var children = load_children_set(parent);
  326. return children.keys;
  327. }
  328. /**
  329. * {@inheritDoc}
  330. */
  331. public bool has_child(Core.EntityPath parent, string child_name) throws StorageError {
  332. var children = load_children_set(parent);
  333. return children.has(child_name);
  334. }
  335. /**
  336. * Loads the children set for a category.
  337. */
  338. private Invercargill.DataStructures.Dictionary<string, bool> load_children_set(Core.EntityPath parent) throws StorageError {
  339. string key = CHILDREN_PREFIX + parent.to_string();
  340. var data = _dbm.get(key);
  341. var result = new Invercargill.DataStructures.Dictionary<string, bool>();
  342. if (data == null) {
  343. return result;
  344. }
  345. var reader = new ElementReader((!) data);
  346. try {
  347. var element = reader.read_element();
  348. if (element.is_null()) {
  349. return result;
  350. }
  351. // The children set is stored as an array of strings
  352. var array = element.as<Invercargill.Enumerable<Invercargill.Element>>();
  353. foreach (var child_element in array) {
  354. if (!child_element.is_null()) {
  355. string child_name = child_element.as<string>();
  356. try {
  357. result.set(child_name, true);
  358. } catch (Invercargill.IndexError e) {
  359. // Skip
  360. }
  361. }
  362. }
  363. } catch (Invercargill.ElementError e) {
  364. throw new StorageError.CORRUPT_DATA("Failed to read children: %s".printf(e.message));
  365. }
  366. return result;
  367. }
  368. /**
  369. * Saves the children set for a category.
  370. */
  371. private void save_children_set(Core.EntityPath parent, Invercargill.DataStructures.Dictionary<string, bool> children) throws StorageError {
  372. string key = CHILDREN_PREFIX + parent.to_string();
  373. uint count = children.count();
  374. if (count == 0) {
  375. _dbm.delete(key);
  376. return;
  377. }
  378. // Store as array of strings
  379. var array = new Invercargill.DataStructures.Vector<Invercargill.Element>();
  380. foreach (var child_name in children.keys) {
  381. var element = new Invercargill.NativeElement<string>(child_name);
  382. array.add(element);
  383. }
  384. var writer = new ElementWriter();
  385. // Use write_element to include type code, so read_element can read it back
  386. writer.write_element(new Invercargill.NativeElement<Invercargill.Enumerable<Invercargill.Element>>(array));
  387. _dbm.set(key, writer.to_binary_data());
  388. }
  389. /**
  390. * {@inheritDoc}
  391. */
  392. public void store_category_config(Core.EntityPath path, string type_label, string expression) throws StorageError {
  393. string key = CONFIG_PREFIX + path.to_string();
  394. var writer = new ElementWriter();
  395. // Use write_element to include type code, so read_element can read it back
  396. writer.write_element(new Invercargill.NativeElement<string>(type_label));
  397. writer.write_element(new Invercargill.NativeElement<string>(expression));
  398. _dbm.set(key, writer.to_binary_data());
  399. }
  400. /**
  401. * {@inheritDoc}
  402. */
  403. public CategoryConfig? get_category_config(Core.EntityPath path) throws StorageError {
  404. string key = CONFIG_PREFIX + path.to_string();
  405. var data = _dbm.get(key);
  406. if (data == null) {
  407. return null;
  408. }
  409. var reader = new ElementReader((!) data);
  410. try {
  411. var label_element = reader.read_element();
  412. var expr_element = reader.read_element();
  413. if (label_element.is_null() || expr_element.is_null()) {
  414. return null;
  415. }
  416. string type_label = label_element.as<string>();
  417. string expression = expr_element.as<string>();
  418. return new CategoryConfig(type_label, expression);
  419. } catch (Invercargill.ElementError e) {
  420. throw new StorageError.CORRUPT_DATA("Failed to read category config: %s".printf(e.message));
  421. }
  422. }
  423. // Key prefix for catalogue configuration
  424. private const string CAT_CONFIG_PREFIX = "catcfg:";
  425. /**
  426. * {@inheritDoc}
  427. */
  428. public void store_catalogue_config(Core.EntityPath path, string type_label, string expression) throws StorageError {
  429. string key = CAT_CONFIG_PREFIX + path.to_string();
  430. var writer = new ElementWriter();
  431. writer.write_element(new Invercargill.NativeElement<string>(type_label));
  432. writer.write_element(new Invercargill.NativeElement<string>(expression));
  433. _dbm.set(key, writer.to_binary_data());
  434. }
  435. /**
  436. * {@inheritDoc}
  437. */
  438. public CatalogueConfig? get_catalogue_config(Core.EntityPath path) throws StorageError {
  439. string key = CAT_CONFIG_PREFIX + path.to_string();
  440. var data = _dbm.get(key);
  441. if (data == null) {
  442. return null;
  443. }
  444. var reader = new ElementReader((!) data);
  445. try {
  446. var label_element = reader.read_element();
  447. var expr_element = reader.read_element();
  448. if (label_element.is_null() || expr_element.is_null()) {
  449. return null;
  450. }
  451. string type_label = label_element.as<string>();
  452. string expression = expr_element.as<string>();
  453. return new CatalogueConfig(type_label, expression);
  454. } catch (Invercargill.ElementError e) {
  455. throw new StorageError.CORRUPT_DATA("Failed to read catalogue config: %s".printf(e.message));
  456. }
  457. }
  458. }
  459. }