From 7631cd8c6c513197635667d568ad665a1646e29b Mon Sep 17 00:00:00 2001 From: "G. Gibson" Date: Sun, 31 May 2026 16:59:27 -0700 Subject: [PATCH] Added merge to DetachedHash/PersistedHas & finalized data sync for database --- gxg-framework.gemspec | 2 +- lib/gxg/gxg_augmented.rb | 13 +- lib/gxg/gxg_database.rb | 203 ++++++++++--------------- lib/gxg/gxg_database_persistedarray.rb | 8 +- lib/gxg/gxg_database_persistedhash.rb | 38 +++-- 5 files changed, 116 insertions(+), 148 deletions(-) diff --git a/gxg-framework.gemspec b/gxg-framework.gemspec index e65c253..9d0467e 100644 --- a/gxg-framework.gemspec +++ b/gxg-framework.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |s| s.name = 'gxg-framework' - s.version = '0.0.69' + s.version = '0.0.70' s.licenses = ['HIPPOCRATIC 2.1', 'https://firstdonoharm.dev/version/2/1/license.html'] s.summary = "GxG Framework" s.description = "GxG Framework" diff --git a/lib/gxg/gxg_augmented.rb b/lib/gxg/gxg_augmented.rb index 35c728c..db59517 100644 --- a/lib/gxg/gxg_augmented.rb +++ b/lib/gxg/gxg_augmented.rb @@ -3238,10 +3238,10 @@ class Struct def self.process(the_struct=::Struct.new(nil),&block) new_struct = ::Struct.new(nil) if block.respond_to?(:call) - if the_struct.is_any?(::Array, ::Hash, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + if the_struct.is_any?(::Hash, ::Array, ::Struct, ::GxG::Database::DetachedHash, ::GxG::Database::PersistedHash, ::GxG::Database::DetachedArray, ::GxG::Database::PersistedArray) new_struct = the_struct.process!(&block) else - raise ArgumentError, "you must pass a Hash, or an Array, or a ByteArray, or a Set, or a Struct" + raise ArgumentError, "you must pass a Hash, or an Array, or a ByteArray, or a Struct" end end new_struct @@ -3250,7 +3250,7 @@ class Struct def self.search(the_struct=::Struct.new(nil),&block) new_struct = ::Struct.new(nil) if block.respond_to?(:call) - if the_struct.is_any?(::Array, ::Hash, ::Set, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + if the_struct.is_any?(::Hash, ::Array, ::Set, ::Struct, ::GxG::Database::DetachedHash, ::GxG::Database::PersistedHash, ::GxG::Database::DetachedArray, ::GxG::Database::PersistedArray) new_struct = the_struct.search(&block) else raise ArgumentError, "you must pass a Hash, or an Array, or a ByteArray, or a Set, or a Struct" @@ -3502,7 +3502,7 @@ class Hash last_container = origin found = false # tester = {:a=>1, :b=>2, :c=>[0, 5], :testing=>{:d=>4.0, :e=>0.9, :f => nil}} - if origin.is_any?(::Hash, ::Array, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + if origin.is_any?(::Hash, ::Array, ::Struct, ::GxG::Database::DetachedHash, ::GxG::Database::PersistedHash, ::GxG::Database::DetachedArray, ::GxG::Database::PersistedArray) origin.process! do |the_value, selector, container| if last_container.object_id != container.object_id container_record = find_container.call(container) @@ -3514,14 +3514,13 @@ class Hash end last_container = container end - safe_key = selector.to_s - # xxx + # xxx --> not using colon in pathnames. # if selector.is_a?(Symbol) # safe_key = (":" + selector.to_s) # else # safe_key = selector.to_s # end - # xxx + safe_key = selector.to_s safe_key = safe_key.gsub("/","%2f") path_stack.unshift(safe_key) # compare the_value diff --git a/lib/gxg/gxg_database.rb b/lib/gxg/gxg_database.rb index f3f725c..01c1df6 100644 --- a/lib/gxg/gxg_database.rb +++ b/lib/gxg/gxg_database.rb @@ -4769,7 +4769,7 @@ module GxG if existing.version > record[:version] action = :provide else - if existing.version < record[:version] + if existing.version <= record[:version] action = :update else action = :ignore @@ -4790,151 +4790,106 @@ module GxG formatting[:constraint] = record[:constraint].to_s end clean_up_list << existing - existing.get_reservation() + existing.wait_for_reservation(100.0) unless existing.write_permission?() && existing.write_reserved?() raise Exception, "Failed to secure write permission and write reservation for this credential." end existing.title = (record[:title]) # ### Update Elements - if record[:content].is_a?(::Hash) - # Review : What to do / how to handle formatted existing Hashes?? - unless existing.format - # Eliminate keys that the imported record does not have. (tracking structural changes) + # ??? + # Formatted/Constrained or Freeform? + if formatting[:format] || formatting[:constraint] + # Formatted + if existing.is_a?(::GxG::Database::PersistedHash) + if existing.format + the_format = existing.format() + if existing.format() != formatting[:format] + raise Exception, "Format Mismatch Error --> Aborting Merge for record uuid #{existing.uuid.inspect}" + end + else + the_format = formatting[:format] + end + existing.format = nil existing.keys.each do |the_key| unless record[:content].keys.include?(the_key) temp_object = existing.delete(the_key) - if temp_object.is_any?(::GxG::Database::PersistedArray, ::GxG::Database::PersistedHash) - temp_object.destroy() - end end end + # + record[:content].keys.each do |the_key| + existing[(the_key)] = record[:content][(the_key)] + existing.set_property_version(the_key, record[:content].property_version(the_key)) + end + # + if the_format + existing.format = the_format + end + existing.version = record[:content].version + existing.save + existing.deactivate + total_records_processed += 1 end - record[:content].each_pair do |property, element_record| - if element_record[:uuid] - # ### Element is Hash or Array - element = self.retrieve_by_uuid(element_record[:uuid], credential) - unless element - # ### Create blank element record and retrieve - # Note: since there is no way to create a Persisted Object with a given UUID, this method is used to create the object: object will be updated later. - unless @connector[(element_record[:type].to_s.downcase.to_sym)].filter({:uuid => element_record[:uuid].to_s}).count > 0 - # This prevents no-element-retrieved-due-to-permission issue causing an 'add duplicate uuid object' situation: - dbid = @connector[(element_record[:type].to_s.downcase.to_sym)].insert({:uuid => element_record[:uuid].to_s, :title => "Merged Persisted Object"}) - self.assign_element_permission(element_record[:type].to_s.downcase.to_sym,dbid,credential,{:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true}) - # - end - element = self.retrieve_by_uuid(element_record[:uuid], credential) + if existing.is_a?(::GxG::Database::PersistedArray) + if existing.constraint() + the_format = existing.constraint() + if existing.constraint() != formatting[:constraint] + raise Exception, "Format Constraint Mismatch Error --> Aborting Merge for record uuid #{existing.uuid.inspect}" end - if element - # ### Ensure Element Linking - # Review : Warning - absence of a :write permission can bugger this up. - clean_up_list << element - element.get_reservation() - unless element.write_permission?() && element.write_reserved?() - raise Exception, "Failed to secure write permission and write reservation for this credential." - end - existing[(property)] = element - # element.deactivate ?? - end - # Note: return to processing queue to set its elements - queue << element_record else - # ### Base Element - unless existing.version(property) > element_record[:version] - the_content = nil - case element_record[:type].to_s.downcase.to_sym - when :element_boolean, :element_integer, :element_float - the_content = element_record[:content] - when :element_bigdecimal - the_content = ::BigDecimal.new(element_record[:content].to_s) - when :element_datetime - the_content = ::DateTime::parse(element_record[:content].to_s) - when :element_text - the_content = element_record[:content].to_s - when :element_binary - the_content = ::GxG::ByteArray.new(element_record[:content].to_s.decode64) - end - existing[(property)] = the_content + the_format = formatting[:constraint] + end + existing.constraint = nil + record[:content].each_with_index do |item, indexer| + existing[(indexer)] = record[:content][(indexer)] + existing.set_element_version(indexer, record[:content].element_version(indexer)) + end + if existing.size > record[:content].size + ((record[:content].size)..(existing.size - 1)).to_a.reverse.each do |indexer| + temp_object = existing.delete_at(indexer) end end + existing.constraint = the_format + existing.version = record[:content].version + existing.save + existing.deactivate + total_records_processed += 1 end else - if record[:content].is_a?(::Array) - # Eliminate elements that the imported record does not have. (tracking structural changes) + # Freeform (rsync) + if existing.is_a?(::GxG::Database::PersistedHash) + existing.format = nil + existing.keys.each do |the_key| + unless record[:content].keys.include?(the_key) + temp_object = existing.delete(the_key) + end + end + # + record[:content].keys.each do |the_key| + existing[(the_key)] = record[:content][(the_key)] + existing.set_property_version(the_key, record[:content].property_version(the_key)) + end + # + existing.version = record[:content].version + existing.save + existing.deactivate + total_records_processed += 1 + end + if existing.is_a?(::GxG::Database::PersistedArray) + record[:content].each_with_index do |item, indexer| + existing[(indexer)] = record[:content][(indexer)] + existing.set_element_version(indexer, record[:content].element_version(indexer)) + end if existing.size > record[:content].size - ((record[:content].size)..(existing.size - 1)).each do |the_indexer| - temp_object = existing.delete_at(the_indexer) - if temp_object.is_any?(::GxG::Database::PersistedArray, ::GxG::Database::PersistedHash) - temp_object.destroy() - end + ((record[:content].size)..(existing.size - 1)).to_a.reverse.each do |indexer| + temp_object = existing.delete_at(indexer) end end - record[:content].each_with_index do |element_record, indexer| - if element_record[:uuid] - # ### Element is Hash or Array - element = self.retrieve_by_uuid(element_record[:uuid], credential) - unless element - # ### Create blank element record and retrieve - # Note: since there is no way to create a Persisted Object with a given UUID, this method is used to create the object: object will be updated later. - unless @connector[(element_record[:type].to_s.downcase.to_sym)].filter({:uuid => element_record[:uuid].to_s}).count > 0 - # This prevents no-element-retrieved-due-to-permission issue causing an 'add duplicate uuid object' situation: - dbid = @connector[(element_record[:type].to_s.downcase.to_sym)].insert({:uuid => element_record[:uuid].to_s, :title => "Merged Persisted Object"}) - self.assign_element_permission(element_record[:type].to_s.downcase.to_sym,dbid,credential,{:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true}) - # - end - element = self.retrieve_by_uuid(element_record[:uuid], credential) - end - if element - # ### Ensure Element Linking - # Review : Warning - absence of a :write permission can bugger this up. - clean_up_list << element - element.get_reservation() - unless element.write_permission?() && element.write_reserved?() - raise Exception, "Failed to secure write permission and write reservation for this credential." - end - existing[(indexer)] = element - # element.deactivate ?? - end - # Note: return to processing queue to set its elements - queue << element_record - else - # ### Base Element - unless existing.version(indexer) > element_record[:version] - the_content = nil - case element_record[:type].to_s.downcase.to_sym - when :element_boolean, :element_integer, :element_float - the_content = element_record[:content] - when :element_bigdecimal - the_content = ::BigDecimal.new(element_record[:content].to_s) - when :element_datetime - the_content = ::DateTime::parse(element_record[:content].to_s) - when :element_text - the_content = element_record[:content].to_s - when :element_binary - the_content = ::GxG::ByteArray.new(element_record[:content].to_s.decode64) - end - existing[(indexer)] = the_content - end - end - # - end + existing.version = record[:content].version + existing.save + existing.deactivate + total_records_processed += 1 end end - # ### Set Fromatting / Constraints - if existing.is_a?(::GxG::Database::PersistedHash) - if formatting[:format].to_s.size > 0 - existing.format = (formatting[:format]) - end - end - if existing.is_a?(::GxG::Database::PersistedArray) - if formatting[:constraint].to_s.size > 0 - existing.constraint = (formatting[:constraint]) - end - end - # ### Conclude Existing - existing.version = (record[:version]) - existing.save - existing.deactivate - total_records_processed += 1 end end end diff --git a/lib/gxg/gxg_database_persistedarray.rb b/lib/gxg/gxg_database_persistedarray.rb index 10d78f4..218cf4d 100644 --- a/lib/gxg/gxg_database_persistedarray.rb +++ b/lib/gxg/gxg_database_persistedarray.rb @@ -1715,7 +1715,7 @@ module GxG end # def element_version(index=nil) - result = 0.0 + result = ::BigDecimal.new("0.0") if index.is_a?(::Numeric) if @element_links[(index)] result = @element_links[(index)][:record][:version] @@ -1810,12 +1810,12 @@ module GxG if @constraint record = @db_address[:database].format_load({:uuid => @constraint.to_s}) if record - record[:ufs] + {:ufs => record[:ufs], :version => record[:version]} else - "" + {} end else - "" + {} end end # diff --git a/lib/gxg/gxg_database_persistedhash.rb b/lib/gxg/gxg_database_persistedhash.rb index 843d718..0ebb212 100644 --- a/lib/gxg/gxg_database_persistedhash.rb +++ b/lib/gxg/gxg_database_persistedhash.rb @@ -41,7 +41,7 @@ module GxG end end # - def element_version(key=nil) + def property_version(key=nil) result = 0.0 if key.is_a?(::Symbol) if key.to_s.size > 256 @@ -59,7 +59,7 @@ module GxG result end # - def set_element_version(element_key, the_version=nil) + def set_property_version(element_key, the_version=nil) result = false if @property_links[(element_key)] if the_version.is_a?(::Numeric) @@ -870,14 +870,13 @@ module GxG end last_container = container end - safe_key = selector.to_s - # xxx + # xxx --> not using colon in pathnames. # if selector.is_a?(Symbol) # safe_key = (":" + selector.to_s) # else # safe_key = selector.to_s # end - # xxx + safe_key = selector.to_s safe_key.gsub!("/","%2f") path_stack.unshift(safe_key) # compare the_value @@ -1426,7 +1425,7 @@ module GxG end end # - def element_version(key=nil) + def property_version(key=nil) result = 0.0 if key.is_a?(::Symbol) if key.to_s.size > 256 @@ -1444,7 +1443,7 @@ module GxG result end # - def set_element_version(element_key, the_version=nil) + def set_property_version(element_key, the_version=nil) result = false if @property_links[(element_key)] if the_version.is_a?(::Numeric) @@ -1469,6 +1468,19 @@ module GxG end end # + def ufs() + if @format + record = @db_address[:database].format_load({:uuid => @format.to_s}) + if record + {:ufs => record[:ufs], :version => record[:version]} + else + {} + end + else + {} + end + end + # def parent() @parent end @@ -2801,11 +2813,13 @@ module GxG end last_container = container end - if selector.is_a?(Symbol) - safe_key = (":" + selector.to_s) - else - safe_key = selector.to_s - end + # xxx --> not using colon in pathnames. + # if selector.is_a?(Symbol) + # safe_key = (":" + selector.to_s) + # else + # safe_key = selector.to_s + # end + safe_key = selector.to_s safe_key.gsub!("/","%2f") path_stack.unshift(safe_key) # compare the_value