gxg-framework/lib/gxg/gxg_database.rb

5531 lines
256 KiB
Ruby

# require sequel here to ensure other mods are underneath it
require_relative "./gxg_sqlite.rb"
require_relative "./gxg_mysql.rb"
require_relative "./gxg_postgres.rb"
require "sequel"
require "securerandom"
require "openssl"
require "base64"
require "net/ldap"
module GxG
DB_SAFETY = ::Mutex.new
DB = {:cache => {}, :formats => {}, :authority => nil, :administrator => nil, :roles => {}}
#
module Database
# Class Place-holder Definitions
class DetachedArray
#
end
class DetachedHash
#
end
class PersistedArray
#
end
class PersistedHash
#
end
#
def self.element_tables()
{
:unspecified => nil,
:element_boolean => [::TrueClass, ::FalseClass, ::NilClass],
:element_integer => [::Integer],
:element_float => [::Float],
:element_bigdecimal => [::BigDecimal],
:element_datetime => [::DateTime],
:element_text => [::String],
:element_binary => [::GxG::ByteArray],
:element_array => [::Array],
:element_hash => [::Hash]
}
end
#
def self.valid_field_classes()
[::TrueClass, ::FalseClass, ::NilClass, ::Integer, ::Float, ::BigDecimal, ::DateTime, ::String, ::GxG::ByteArray]
end
def self.element_table_index(the_key=:unspecified)
(::GxG::Database::element_tables().keys.index(the_key.to_sym) || 0)
end
#
def self.element_table_by_index(the_index=0)
(::GxG::Database::element_tables().keys[(the_index)] || :unspecified)
end
#
def self.element_table_for_instance(the_instance=nil)
result = :unspecified
#
table = ::GxG::Database::element_tables()
table.keys.each do |the_key|
unless the_key == :unspecified
if the_instance.is_any?(table[(the_key)])
result = the_key
break
end
end
end
#
result
end
#
def self.persistable?(the_object)
result = true
unless the_object.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
if the_object.is_any?(::Hash, ::Array)
the_object.search do |item, selector, container|
if result
if ::GxG::Database::element_table_for_instance(item) == :unspecified
result = false
break
end
end
nil
end
else
if ::GxG::Database::element_table_for_instance(the_object) == :unspecified
result = false
end
end
end
result
end
#
def self.iterative_detached_persist(old_root=nil)
# New PersistedArray, or PersistedHash interface:
#
result = nil
begin
# db open?
# write permissions on db?
unless old_root.is_any?(::Array, ::Hash)
raise ArgumentError, "You MUST provide either an Array or a Hash."
end
unless GxG::Database::persistable?(old_root)
raise ArgumentError, "The object you are attempting to persist contains a non-persistable item."
end
#
if old_root.is_a?(::Array)
original_partner = ::GxG::Database::DetachedArray.new()
#
end
if old_root.is_a?(::Hash)
original_partner = ::GxG::Database::DetachedHash.new()
end
#
paring_data = [{:parent => nil, :parent_selector => nil, :object => old_root, :partner => original_partner}]
children_of = Proc.new do |the_parent=nil|
list = []
paring_data.each do |node|
if node[:parent].object_id == the_parent.object_id
list << node
end
end
list
end
#
parent_of = Proc.new do |the_parent=nil|
output = nil
paring_data.each do |entry|
if entry[:object].object_id == the_parent.object_id
output = entry
end
end
output
end
find_partner = Proc.new do |the_object|
found_partner = nil
paring_data.each do |the_record|
if the_record.is_a?(::Hash)
if the_record[:object].object_id == the_object.object_id
found_partner = the_record[:partner]
break
end
end
end
found_partner
end
# build paring data:
delegate_permission = nil
old_root.search do |the_value, the_selector, the_container|
if the_value.is_a?(::Array)
paring_data << {:parent => the_container, :parent_selector => the_selector, :object => the_value, :partner => ::GxG::Database::DetachedArray.new()}
else
if the_value.is_a?(::Hash)
paring_data << {:parent => the_container, :parent_selector => the_selector, :object => the_value, :partner => ::GxG::Database::DetachedHash.new()}
else
paring_data << {:parent => the_container, :parent_selector => the_selector, :object => the_value, :partner => the_value}
end
end
end
#
# Assign objects to structure in order by parent / parent_selector
link_db = [(paring_data[0])]
while link_db.size > 0
entry = link_db.shift
if entry.is_a?(::Hash)
if entry[:object].is_any?(::Array, ::Hash)
# get children and assign
children = children_of.call(entry[:object])
children.each do |child|
entry[:partner][(child[:parent_selector])] = child[:partner]
if child[:partner].is_any?(::GxG::Database::DetachedArray, ::GxG::Database::DetachedHash)
link_db << child
end
end
end
end
end
result = original_partner
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:object => old_root}})
end
result
end
#
def self.detached_format_find(the_criteria={})
result = nil
tests = {}
[:uuid, :ufs, :title, :version].each do |the_key|
if the_criteria[(the_key)]
tests[(the_key)] = the_criteria[(the_key)]
end
end
GxG::DB[:formats].each_pair do |uuid,record|
score = 0
tests.keys.each do |the_test|
if record[(the_test)] == tests[(the_test)]
score += 1
end
end
if tests.keys.size == score
result = record
break
end
end
result
end
#
def self.detached_format_load(the_criteria={}, &block)
# If already loaded and block provided: return an Array of format records.
# Load any missing formats from server, and call block if provided.
# Note: results are in NO particular order (DO NOT INDEX into the result set from source criteria)
success = false
needed = []
results = []
if the_criteria.is_a?(::Hash)
loaded = ::GxG::Database::detached_format_find(the_criteria)
if loaded
results << loaded
if block.respond_to?(:call)
block.call(results)
end
success = true
else
needed << the_criteria
end
else
if the_criteria.is_a?(::Array)
the_criteria.each do |the_specifier|
loaded = ::GxG::Database::detached_format_find(the_specifier)
if loaded
results << loaded
else
needed << the_specifier
end
end
end
end
if needed.size > 0
if ::GxG::DB[:roles][:format].is_a?(::GxG::Database::Database)
needed.each do |the_specifier|
format_record = ::GxG::DB[:roles][:format].format_load({:uuid => the_specifier.to_s.to_sym})
if format_record
GxG::DB[:formats][(format_record[:uuid].to_sym)] = format_record
results << format_record
end
end
if block.respond_to?(:call)
block.call(results)
end
success = true
end
else
if results.size > 0
if block.respond_to?(:call)
block.call(results)
end
success = true
end
end
#
success
end
#
def self.process_detached_import(import_manifest=nil, options={})
result = []
if import_manifest.is_a?(::Hash)
if import_manifest[:formats].size > 0
# import formats: GxG::DB[:formats]
import_manifest[:formats].keys.each do |the_key|
unless GxG::DB[:formats][(the_key)]
# Review do ::Hash.gxg_import on format content
format_record = import_manifest[:formats][(the_key)]
if format_record[:version].is_a?(::String)
format_record[:version] = BigDecimal.new(format_record[:version])
end
format_record[:content] = ::Hash.gxg_import(format_record[:content])
GxG::DB[:formats][(the_key)] = format_record
end
end
end
#
if import_manifest[:records].size > 1
import_manifest[:records].each do |the_record|
result << GxG::Database::DetachedHash::import(the_record)
end
else
result << GxG::Database::DetachedHash::import(import_manifest[:records][0])
end
end
result
end
#
def self.process_import(import_manifest=nil, credential=nil, options={})
# sync_import(credential=nil, the_records={:formats => {}, :records => []}, options={})
result = false
if import_manifest.is_a?(::Hash) && ::GxG::valid_uuid?(credential) && options.is_a?(::Hash)
options = {:role => :data}.merge!(options)
database = ::GxG::DB[:roles][(options[:role])]
if database.is_a?(::GxG::Database::Database)
if ::GxG::DB[:roles][:format].is_a?(::GxG::Database::Database)
unless database == ::GxG::DB[:roles][:format]
::GxG::DB[:roles][:format].sync_import(credential, {:formats => import_manifest[:formats], :records => []}, options)
end
end
#
result = database.sync_import(credential, import_manifest, options)
else
log_warn("No suitable database of role #{options[:role].inspect} could be found for the import request : Ignoring Request")
end
else
log_warn("Invalid parameters to import request : Ignoring Request")
end
result
end
#
def self.as_import_record(records=[], options={})
result = {:formats => {}, :records => []}
if records.is_any?(::GxG::Database::DetachedHash, ::GxG::Database::PersistedHash)
records = [(records)]
end
if records.is_any?(::Array, ::GxG::Database::DetachedArray, ::GxG::Database::PersistedArray)
records.each do |the_object|
if the_object.is_any?(::GxG::Database::DetachedHash, ::GxG::Database::PersistedHash)
object_record = the_object.export()
object_record.search do |item,selector,container|
if selector == :format || selector == :constraint
if item.to_s.size > 0
format_uuid = item
unless result[:formats][(format_uuid.to_s.to_sym)].is_a?(::Hash)
if ::GxG::DB[:roles][:format].is_a?(::GxG::Database::Database)
format_sample = ::GxG::DB[:roles][:format].format_load({:uuid => format_uuid.to_s.to_sym})
format_sample[:content] = format_sample[:content].gxg_export()
result[:formats][(format_uuid.to_s.to_sym)] = format_sample
end
end
end
end
end
result[:records] << object_record
#
end
end
end
result
end
#
def self.connect(the_url, options={})
# TODO: require :credential (access privs)
result = nil
begin
# check for URI ipv6 [format] compatibility
db_url = ::URI::parse(the_url.to_s)
the_path = db_url.path.to_s
if the_path == ""
the_path = db_url.hostname.to_s
end
if ::RUBY_ENGINE == "jruby"
# jdbc:sqlite::memory:
# jdbc:postgresql://localhost/database?user=username
# jdbc:mysql://localhost/test?user=root&password=root
# jdbc:h2:mem:
# jdbc:hsqldb:mem:mymemdb
# jdbc:derby:memory:myDb;create=true
# jdbc:sqlserver://localhost;database=sequel_test;integratedSecurity=true
# jdbc:jtds:sqlserver://localhost/sequel_test;user=sequel_test;password=sequel_test
# jdbc:oracle:thin:user/password@localhost:1521:database
# jdbc:db2://localhost:3700/database:user=user;password=password;
# jdbc:sqlanywhere://localhost?DBN=Test;UID=user;PWD=password
case db_url.scheme.to_sym
when :sqlite, :sqlite3
if ["/memory", "memory"].include?(the_path)
result = Sequel.connect("jdbc:sqlite::memory:")
else
result = Sequel.connect("jdbc:sqlite:" + the_path)
end
when :mysql, :mysql2, :mariadb
result = Sequel.connect(("jdbc:mysql://" + db_url.hostname.to_s + "/" + db_url.path.to_s.split("/").last.to_s + "?user=" + db_url.user.to_s + "&password=" + db_url.password.to_s + "&serverTimezone=UTC"))
else
raise Exception, "As-yet unsupported adapter type --> Under Construction"
end
#
else
case db_url.scheme.to_sym
when :sqlite, :sqlite3
if ["/memory", "memory"].include?(the_path)
result = Sequel.connect("sqlite::memory:")
else
result = Sequel.connect("sqlite://" + the_path)
end
else
# auto-forward to mysql2 gem if mysql:// scheme
if db_url.scheme.to_s == "mysql"
#
result = Sequel.connect({:adapter => "mysql2", :host => (db_url.hostname.to_s), :database => (db_url.path.to_s.split("/").last), :user => (db_url.user.to_s), :password => (db_url.password.to_s)})
#
else
#
result = Sequel.connect({:adapter => (db_url.scheme.to_s), :host => (db_url.hostname.to_s), :database => (db_url.path.to_s.split("/").last), :user => (db_url.user.to_s), :password => (db_url.password.to_s)})
#
end
end
end
result = ::GxG::Database::Database.new(result, options.merge({:url => (db_url)}))
rescue => the_error
log_error({:error => the_error, :parameters => {:url => (the_url), :options => (options)}})
end
result
end
# External Authority (Such as LDAP)
class ExternalAuthority
def alive?()
true
end
end
#
class LDAPAuthority < ::GxG::Database::ExternalAuthority
#
def initialize(settings={})
# Settings: {:host => "localhost", :port => 389, :base_dn => "cn=admin, dc=my-domain, dc=com", :password => "secret-word"}
@active = false
@connector = ::Net::LDAP.new
@connector.host = ::TCPSocket.getaddress(settings[:host].to_s)
@connector.port = settings[:port].to_s.to_i
@connector.auth settings[:base_dn].to_s, settings[:password].to_s
if @connector.bind
@active = true
else
raise "Unable to bind to LDAP host."
end
self
end
#
def open?()
@active
end
#
end
# Database class proper
class Database
def self.connect(the_url, options={})
::GxG::Database::connect(the_url, options)
end
#
def self.element_tables()
{
:unspecified => nil,
:element_boolean => [::TrueClass, ::FalseClass, ::NilClass],
:element_integer => [::Integer],
:element_float => [::Float],
:element_bigdecimal => [::BigDecimal],
:element_datetime => [::DateTime],
:element_text => [::String],
:element_binary => [::GxG::ByteArray],
:element_array => [::Array],
:element_hash => [::Hash]
}
end
#
def self.valid_field_classes()
[::TrueClass, ::FalseClass, ::NilClass, ::Integer, ::Float, ::BigDecimal, ::DateTime, ::String, ::GxG::ByteArray]
end
#
def self.element_table_index(the_key=:unspecified)
(::GxG::Database::Database::element_tables().keys.index(the_key.to_sym) || 0)
end
#
def self.element_table_by_index(the_index=0)
(::GxG::Database::Database::element_tables().keys[(the_index)] || :unspecified)
end
#
def self.element_table_for_instance(the_instance=nil)
# Review : rewrite - in use
result = :unspecified
#
table = ::GxG::Database::Database::element_tables()
table.keys.each do |the_key|
unless the_key == :unspecified
if the_instance.is_any?(table[(the_key)])
result = the_key
break
end
end
end
#
result
end
#
def db_migration()
# Review : Consider adding support for a ::Symbol class as well. (ease-of-use implications later)
# Internal mapping of the database format
# Datatypes - See: http://sequel.jeremyevans.net/rdoc/files/doc/schema_modification_rdoc.html
# Column Options - See: http://sequel.jeremyevans.net/rdoc/classes/Sequel/Database.html#method-i-add_column
# See also : http://ruby-doc.org/core-2.2.0/Array.html#method-i-pack
{
:element_text => {
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
:uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256},
:length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}
},
:text_page => {
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
:parent_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:ordinal => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:content => {:type => :varchar, :options => {:size => 4099, :default => "", :null => false}, :bytes => 4099}
},
:element_binary => {
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
:uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256},
:format => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}
},
:binary_page => {
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
:parent_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:ordinal => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:content => {:type => :blob, :options => {:size => 65536}, :bytes => 65536}
},
:element_array => {
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
:uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:constraint => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:title => {:type => :varchar, :options => {:size => 259, :null => false, :default => "Untitled"}, :bytes => 259},
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}
},
:array_elements => {
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
:parent_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:ordinal => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256},
:element => {:type => :varchar, :options => {:size => 259, :default => "element_boolean", :null => false}, :bytes => 259},
:element_boolean => {:type => :integer, :options => {:size => 1, :default => -1}, :bytes => ([ -1 ].pack('l!').length)},
:element_integer => {:type => :integer, :options => {}, :bytes => ([ -1 ].pack('l!').length)},
:element_float => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
:element_bigdecimal => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256},
:element_datetime => {:type => :datetime, :options => {}, :bytes => 16},
:time_offset => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
:time_prior => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
:time_after => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
:length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:element_text => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
:element_text_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:element_binary_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:element_array_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:element_hash_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}
},
:element_hash => {
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
:uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:format => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:title => {:type => :varchar, :options => {:size => 259, :null => false, :default => "Untitled"}, :bytes => 259},
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}
},
:hash_properties => {
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
:parent_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:property => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
:ordinal => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256},
:element => {:type => :varchar, :options => {:size => 259, :default => "element_boolean", :null => false}, :bytes => 259},
:element_boolean => {:type => :integer, :options => {:size => 1, :default => -1}, :bytes => ([ -1 ].pack('l!').length)},
:element_integer => {:type => :integer, :options => {}, :bytes => ([ -1 ].pack('l!').length)},
:element_float => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
:element_bigdecimal => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256},
:element_datetime => {:type => :datetime, :options => {}, :bytes => 16},
:time_offset => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
:time_prior => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
:time_after => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
:length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:element_text => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
:element_text_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:element_binary_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:element_array_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:element_hash_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}
},
:element_locks => {
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
:type => {:type => :varchar, :options => {:size => 40,:default => "", :null => false}, :bytes => 40},
:reservation => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:credential => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:element_table => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
:elementid => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}
},
:trash => {
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
:element_table => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
:elementid => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}
},
:users => {
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
:uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:user_id => {:type => :varchar, :options => {:size => 259, :null => false, :default => ""}, :bytes => 259},
:password_hash => {:type => :varchar, :options => {:size => 259, :null => false, :default => ""}, :bytes => 259},
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}
},
:roles => {
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
:uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:seo => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
:title => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}
},
:user_roles => {
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
:user_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:role_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}
},
:permissions => {
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
:credential => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:execute => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:rename => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:move => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:destroy => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:create => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:write => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:read => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:element_table => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
:elementid => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}
},
:file_permissions => {
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
:file_id => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:credential => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:execute => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:rename => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:move => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:destroy => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:create => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:write => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:read => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}
},
:formats => {
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
:uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:type => {:type => :varchar, :options => {:size => 40, :null => false, :default => "structure"}, :bytes => 40},
:ufs => {:type => :varchar, :options => {:size => 4099, :default => "", :null => false}, :bytes => 4099},
:title => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256},
:mime_types => {:type => :varchar, :options => {:size => 4099, :default => "", :null => false}, :bytes => 4099}
},
:settings => {
:dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
:uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
:title => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
:version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}
}
}
end
#
def db_format(format_options={})
# MySql stuff:
# ALTER TABLE tbl_name MAX_ROWS=1000000000 AVG_ROW_LENGTH=nnn;
# See: https://dev.mysql.com/doc/refman/5.0/en/table-size-limit.html
result = true
#
begin
if @connector.is_a?(::Sequel::Database)
case @scheme
when :sqlite
@connector.run("PRAGMA page_size=65536")
@connector.run("PRAGMA max_page_count=281474976710656")
@connector.run("PRAGMA encoding='UTF-8'")
when :mysql, :mysql2
@connector.run("SET GLOBAL max_allowed_packet = 17825792;")
@connector.run("SET NAMES 'utf8' COLLATE 'utf8_general_ci';")
@connector.run("SET collation_connection = 'utf8_general_ci';")
when :postgres
# place holder
end
migration = db_migration()
migration.keys.each do |the_table|
unless @connector.table_exists?(the_table)
@connector.create_table(the_table) do
primary_key :dbid
end
end
used_bytes = 0
#
existing_columns = @connector[(the_table)].columns
migration[(the_table)].keys.each do |the_column|
used_bytes += (migration[(the_table)][(the_column)][:bytes]).to_i
if existing_columns.include?(the_column)
# TODO: figure out a way to inspect columns for attributes -> detailed verification
else
# collation for mysql
if @scheme == :mysql
# Note: might have to specify :collate first, use -> {:collate => ""}.merge(migration[(the_table)][(the_column)][:options])
if migration[(the_table)][(the_column)][:type] == :varchar
# migration[(the_table)][(the_column)][:options] = {:collate => "utf8_general_ci"}.merge(migration[(the_table)][(the_column)][:options])
migration[(the_table)][(the_column)][:options][:collate] = "utf8_general_ci"
end
if migration[(the_table)][(the_column)][:type] == :blob
# migration[(the_table)][(the_column)][:options] = {:collate => "binary"}.merge(migration[(the_table)][(the_column)][:options])
# Note: MySQL 5 doesn't like putting collation on blobs! (blows up)
# migration[(the_table)][(the_column)][:options][:collate] = "binary"
end
else
end
# do the deed
@connector.add_column((the_table), (the_column), (migration[(the_table)][(the_column)][:type]), (migration[(the_table)][(the_column)][:options]))
#
end
end
#
if @scheme == :mysql
# @connector.run("ALTER TABLE #{the_table.to_s} MAX_ROWS=18446744073709551616 AVG_ROW_LENGTH=#{used_bytes.to_s};")
end
#
end
else
raise Exception, "Invalid db connection"
end
rescue Exception => the_error
result = false
log_error({:error => the_error, :parameters => format_options})
end
#
result
end
#
def db_formatted?()
result = true
#
begin
if @connector.is_a?(::Sequel::Database)
migration = db_migration()
migration.keys.each do |the_table|
unless @connector.table_exists?(the_table)
result = false
break
end
#columns
existing_columns = @connector[(the_table)].columns
migration[(the_table)].keys.each do |the_column|
unless existing_columns.include?(the_column)
result = false
break
end
end
#
unless (result)
break
end
end
else
raise Exception, "Invalid db connection"
end
rescue Exception => the_error
result = false
log_error({:error => the_error, :parameters => nil})
end
#
result
end
#
def open?()
begin
@connector.synchronize do |connection|
@active = @connector.valid_connection?(connection)
end
rescue => the_error
@active = false
log_error({:error => the_error, :parameters => nil})
end
@active
end
#
def close()
# place holder for now.
if @authority
@authority.db_unregister(self)
end
end
#
def connector()
@connector
end
#
#
def to_uri()
::URI.parse(@url.to_s)
end
#
def inspect()
"<Database: #{self.to_uri()}>"
end
# ### Permission Handling methods
def db_permissions
@base_permissions.clone
end
# ### Authority support
def db_register(the_db)
if the_db.is_a?(::GxG::Database::Database)
unless @db_list.find_index(the_db)
@db_list << the_db
end
end
end
#
def db_unregister(the_db)
if the_db.is_a?(::GxG::Database::Database)
if @db_list.find_index(the_db)
@db_list.delete_at(@db_list.find_index(the_db))
end
end
end
#
def db_list()
result = []
@db_list.each do |the_db|
result << the_db
end
result
end
# ### User, Role, and Group methods
#
def user_manifest(include_password=false)
result = []
if @authority
result = @authority.user_manifest(include_password)
else
if self.alive?
if self.open?
begin
if include_password == true
dataset = @connector[:users].select(:uuid, :user_id, :password_hash, :version).all
else
dataset = @connector[:users].select(:uuid, :user_id, :version).all
end
dataset.each do |entry|
result << entry
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {}})
end
end
end
end
result
end
#
def user_id_available?(the_user_id)
result = false
if @authority
result = @authority.user_id_available?(the_user_id)
else
if self.alive?
if self.open?
begin
if the_user_id.to_s.size <= 256
if @connector[:users].filter({:user_id => (the_user_id.to_s)}).count == 0
result = true
end
else
raise ArgumentError, "Oversized UserID attempted: #{the_user_id}"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:user_id => the_user_id}})
end
end
end
end
result
end
#
def user_exist?(the_user_id)
result = false
if @authority
result = @authority.user_exist?(the_user_id)
else
if self.alive?
if self.open?
begin
if ::GxG::valid_uuid?(the_user_id)
if @connector[:users].filter({:uuid => (the_user_id.to_s)}).count > 0
result = true
end
else
if the_user_id.to_s.size <= 256
if @connector[:users].filter({:user_id => (the_user_id.to_s)}).count > 0
result = true
end
else
raise ArgumentError, "Oversized UserID attempted: #{the_user_id}"
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:user_id => the_user_id}})
end
end
end
end
result
end
#
def user_credential(the_user_id="", the_password="")
result = nil
if @authority
result = @authority.user_credential(the_user_id, the_password)
else
if self.alive?
if self.open?
begin
if the_user_id.to_s.size <= 256
if the_password.to_s.size > 0
record = @connector[:users].select(:uuid, :user_id, :password_hash).where({:user_id => (the_user_id.to_s)}).first
if record
salt = record[:password_hash].split("\t")[0].to_s
pbkdf2 = ::Base64.decode64(record[:password_hash].split("\t")[1].to_s)
tester = ::OpenSSL::PKCS5::pbkdf2_hmac_sha1(the_password.to_s, salt, 1000, pbkdf2.length)
if pbkdf2 == tester
result = record[:uuid].to_sym
else
log_warn("Password attempt invalid for #{the_user_id}")
end
else
raise Exception, "UserID not found: #{the_user_id}"
end
else
raise Exception, "Empty password not supported"
end
else
raise Exception, "Oversized UserID attempted: #{the_user_id}"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:user_id => the_user_id}})
end
end
end
end
result
end
#
def user_create(the_user_id="", the_password="", use_credential=nil)
result = false
if @authority
result = @authority.user_create(the_user_id, the_password)
else
if self.alive?
if self.open?
if self.db_permissions()[:write]
begin
if the_user_id.to_s.size <= 256
if the_password.to_s.size > 0
if self.user_id_available?(the_user_id)
if ::GxG::valid_uuid?(use_credential)
the_uuid = use_credential.to_s
if (@connector[:users].filter({:uuid => (the_uuid.to_s)}).count > 0)
raise Exception, "That credential UUID is already in use"
end
else
the_uuid = ::GxG::uuid_generate()
while (@connector[:users].filter({:uuid => (the_uuid.to_s)}).count > 0)
the_uuid = ::GxG::uuid_generate()
end
end
salt = ::SecureRandom.base64(24)
pbkdf2 = ::OpenSSL::PKCS5::pbkdf2_hmac_sha1(the_password, salt, 1000, 24)
the_pass_hash = [salt, ::Base64.strict_encode64(pbkdf2)].join("\t")
@connector[:users].insert({:uuid => (the_uuid), :user_id => (the_user_id.to_s), :password_hash => (the_pass_hash)})
result = true
else
raise Exception, "UserID not available: #{the_user_id}"
end
else
raise ArgumentError, "Empty password not supported"
end
else
raise ArgumentError, "Oversized UserID attempted: #{the_user_id}"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:user_id => the_user_id}})
end
end
end
end
end
result
end
#
def user_update(credential=nil, user_id=nil)
result = false
if @authority
result = @authority.user_update(credential, user_id)
else
if self.alive?
if self.open?
if self.db_permissions()[:write]
begin
if ::GxG::valid_uuid?(credential)
if self.user_id_available?(user_id.to_s)
record = @connector[:users].filter({:uuid => (credential.to_s)}).first
if record
@connector[:users].filter({:uuid => (credential.to_s)}).update({:user_id => (user_id), :version => (record[:version] + 0.0001)})
result = true
else
raise Exception, "User record not found"
end
else
log_warn("UserID is not available: #{user_id.to_s}")
end
else
raise ArgumentError, "You must supply a valid user credential (as a String or Symbol)"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:credential => credential, :user_id => user_id}})
end
end
end
end
end
result
end
#
def user_set_password(credential=nil, old_password="", new_password="")
result = false
if @authority
result = @authority.user_set_password(credential, old_password, new_password)
else
if self.alive?
if self.open?
if self.db_permissions()[:write]
begin
if ::GxG::valid_uuid?(credential)
if new_password.to_s.size > 0
if @connector[:users].filter({:uuid => (credential.to_s)}).count > 0
record = @connector[:users].filter({:uuid => (credential.to_s)}).first
if record
if self.user_credential(record[:user_id], old_password).to_s == credential.to_s
salt = ::SecureRandom.base64(24)
pbkdf2 = ::OpenSSL::PKCS5::pbkdf2_hmac_sha1(new_password, salt, 1000, 24)
the_pass_hash = [salt, ::Base64.strict_encode64(pbkdf2)].join("\t")
@connector[:users].filter({:uuid => (credential.to_s)}).update({:password_hash => (the_pass_hash), :version => (BigDecimal(record[:version].to_s) + 0.0001)})
result = true
else
log_warn("Invalid password change attempted for #{credential}")
end
else
raise Exception, "Could not retrieve user record: #{credential}"
end
else
raise Exception, "No user with that credential exists"
end
else
raise ArgumentError, "Empty password not supported"
end
else
raise ArgumentError, "Invalid credential argument: #{credential}"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:credential => credential}})
end
end
end
end
end
result
end
#
def user_destroy(credential=nil)
result = false
if @authority
result = @authority.user_destroy(credential)
else
if self.alive?
if self.open?
if self.db_permissions()[:write]
begin
if ::GxG::valid_uuid?(credential)
if @connector[:users].filter({:uuid => (credential.to_s)}).count > 0
the_list = self.db_list()
the_list << (self)
the_list.each do |the_db|
the_db.connector()[:permissions].filter({:credential => (credential.to_s)}).delete
the_db.connector()[:user_roles].filter({:user_uuid => (credential.to_s)}).delete
end
@connector[:users].filter({:uuid => (credential.to_s)}).delete
result = true
end
else
raise ArgumentError, "Invalid credential argument: #{credential}"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:credential => credential}})
end
end
end
end
end
result
end
#
def user_fetch(search_options={})
result = nil
if @authority
result = @authority.user_fetch(search_options)
else
if self.alive?
if self.open?
begin
result = @connector[:users].filter(search_options).first
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:credential => (credential)}})
end
end
end
end
result
end
#
def user_roles(credential=nil)
result = []
if @authority
result = @authority.user_roles(credential)
else
if self.alive?
if self.open?
begin
if ::GxG::valid_uuid?(credential)
memberships = @connector[:user_roles].select(:role_uuid).where({:user_uuid => (credential.to_s)})
pull_list = []
memberships.each do |record|
unless pull_list.find_index(record[:role_uuid].to_sym)
pull_list << record[:role_uuid].to_sym
end
end
pull_list.each do |role_uuid|
record = @connector[:roles].filter({:uuid => (role_uuid.to_s)})
if record
result << {:credential => (role_uuid), :title => (record[:title]), :seo => (record[:seo])}
end
end
else
raise ArgumentError, "You must supply a valid user credential (as a String or Symbol)"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:credential => (credential)}})
end
end
end
end
result
end
#
def role_manifest()
result = []
#
if @authority
result = @authority.roles()
else
if self.alive?
if self.open?
@connector[:roles].all.each do |record|
result << {:uuid => record[:uuid].to_s.to_sym, :title => record[:title], :seo => record[:seo], :version => BigDecimal(record[:version].to_s)}
end
end
end
end
#
result
end
#
def role_exist?(credential=nil)
result = nil
if @authority
result = @authority.role_exist?(credential)
else
if self.alive?
if self.open?
if ::GxG::valid_uuid?(credential)
if @connector[:roles].filter({:uuid => (credential.to_s)}).count > 0
result = true
end
else
if @connector[:roles].filter({:title => (credential.to_s)}).count > 0
result = true
end
end
end
end
end
result
end
#
def role_create(title="", seo="")
result = nil
if @authority
result = @authority.role_create(title,seo)
else
if self.alive?
if self.open?
if self.db_permissions()[:write]
begin
#
if title.to_s.size <= 256
#
if title.to_s.size == 0
raise ArgumentError, "You MUST supply a title for the role."
end
unless seo.to_s.size <= 256
raise ArgumentError, "Oversized SEO attempted (256 Character Limit): #{seo}"
end
if seo.to_s.size == 0
seo = title.downcase.gsub(" ","_")[0..255]
end
the_uuid = ::GxG::uuid_generate()
while (@connector[:roles].filter({:uuid => (the_uuid.to_s)}).count > 0)
the_uuid = ::GxG::uuid_generate()
end
@connector[:roles].insert({:uuid => (the_uuid.to_s), :title => (title.to_s), :seo => (seo.to_s)})
result = the_uuid.to_sym
else
raise ArgumentError, "Oversized title attempted (256 Character Limit): #{title}"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:title => (title), :seo => (seo)}})
end
end
end
end
end
result
end
#
def role_update(role_uuid=nil, data={})
# data: :title, :seo
result = false
if @authority
result = @authority.role_update(role_uuid,data)
else
if self.alive?
if self.open?
if self.db_permissions()[:write]
begin
unless data.is_a?(::Hash)
raise Exception, "Update data must be passed in a Hash"
end
if ::GxG::valid_uuid?(role_uuid)
record = @connector[:roles].filter({:uuid => (role_uuid.to_s)}).first
if record
the_title = (data[:title] || record[:title].to_s)
the_seo = (data[:seo] || the_title.downcase.gsub(" ", "_"))
#
@connector[:roles].filter({:uuid => (role_uuid.to_s)}).update({:title => (the_title), :seo => (the_seo), :version => (BigDecimal(record[:version].to_s) + 0.0001)})
result = true
else
raise Exception, "Attempted to update a non-existent role: #{role_uuid}"
end
else
raise ArgumentError, "You must specify a valid role UUID (as a String or Symbol)"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:role_uuid => (role_uuid), :data => (data)}})
end
end
end
end
end
result
end
#
def role_destroy(role_uuid=nil)
result = false
if @authority
result = @authority.role_destroy(role_uuid)
else
if self.alive?
if self.open?
if self.db_permissions()[:write]
begin
if ::GxG::valid_uuid?(role_uuid)
# cannot destroy if still linked as group's default role
record = @connector[:roles].filter({:uuid => (role_uuid.to_s)}).first
if record
the_list = self.db_list()
the_list << (self)
the_list.each do |the_db|
the_db.connector()[:permissions].filter({:credential => (role_uuid.to_s)}).delete
the_db.connector()[:user_roles].filter({:role_uuid => (role_uuid.to_s)}).delete
end
@connector[:roles].filter({:uuid => (role_uuid.to_s)}).delete
result = true
end
else
raise ArgumentError, "You must specify a valid role UUID (as a String or Symbol)"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:role_uuid => (role_uuid)}})
end
end
end
end
end
result
end
#
def role_add_user(role_uuid=nil, credential=nil)
result = false
if @authority
result = @authority.role_add_user(role_uuid, credential)
else
if self.alive?
if self.open?
if self.db_permissions()[:write]
begin
if ::GxG::valid_uuid?(role_uuid)
if ::GxG::valid_uuid?(credential)
if @connector[:user_roles].filter({:role_uuid => (role_uuid.to_s), :user_uuid => (credential.to_s)}).count == 0
@connector[:user_roles].insert({:role_uuid => (role_uuid.to_s), :user_uuid => (credential.to_s)})
end
result = true
else
raise ArgumentError, "You must specify a valid user credential UUID (as a String or Symbol)"
end
else
raise ArgumentError, "You must specify a valid role UUID (as a String or Symbol)"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:role_uuid => (role_uuid), :credential => (credential)}})
end
end
end
end
end
result
end
#
def role_remove_user(role_uuid=nil, credential=nil)
result = false
if @authority
result = @authority.role_remove_user(role_uuid, credential)
else
if self.alive?
if self.open?
if self.db_permissions()[:write]
begin
if ::GxG::valid_uuid?(role_uuid)
if ::GxG::valid_uuid?(credential)
if @connector[:user_roles].filter({:role_uuid => (role_uuid.to_s), :user_uuid => (credential.to_s)}).count > 0
@connector[:user_roles].filter({:role_uuid => (role_uuid.to_s), :user_uuid => (credential.to_s)}).delete
end
result = true
else
raise ArgumentError, "You must specify a valid user credential UUID (as a String or Symbol)"
end
else
raise ArgumentError, "You must specify a valid role UUID (as a String or Symbol)"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:role_uuid => (role_uuid), :credential => (credential)}})
end
end
end
end
end
result
end
#
def role_member?(role_uuid=nil, credential=nil)
result = false
if @authority
result = @authority.role_member?(role_uuid, credential)
else
if self.alive?
if self.open?
begin
if ::GxG::valid_uuid?(role_uuid)
if ::GxG::valid_uuid?(credential)
if @connector[:user_roles].filter({:role_uuid => (role_uuid.to_s), :user_uuid => (credential.to_s)}).count > 0
result = true
end
else
raise ArgumentError, "You must specify a valid user credential UUID (as a String or Symbol)"
end
else
raise ArgumentError, "You must specify a valid role UUID (as a String or Symbol)"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:role_uuid => (role_uuid), :credential => (credential)}})
end
end
end
end
result
end
#
def role_members(role_uuid=nil)
result = []
if @authority
result = @authority.role_members(role_uuid)
else
if self.alive?
if self.open?
if GxG::valid_uuid?(role_uuid)
@connector[:user_roles].filter({:role_uuid => (role_uuid.to_s)}).each do |the_record|
unless result.include?(the_record[:user_uuid].to_s.to_sym)
result << the_record[:user_uuid].to_s.to_sym
end
end
end
end
end
end
result
end
#
def role_fetch(search_options={})
result = nil
if @authority
result = @authority.role_fetch(search_options)
else
if self.alive?
if self.open?
result = @connector[:roles].filter(search_options).first
end
end
end
result
end
#
# ### Raw Permission Handling methods
def element_permissions(table=:unspecified, dbid=0, options={})
result = []
begin
unless self.alive?
raise Exception, "Database inactive"
end
unless self.open?
raise Exception, "Database not available"
end
unless dbid.is_a?(::Integer)
raise ArgumentError, "dbid must be specified with a Integer"
end
unless options.is_a?(::Hash)
raise ArgumentError, "options must be specified with a Hash"
end
credential = options[:credential]
if credential
unless ::GxG::valid_uuid?(credential)
raise ArgumentError, "This option requires a valid credential UUID (as a String or Symbol)"
end
end
if table.is_a?(::Integer)
table = ::GxG::Database::Database::element_table_by_index(table)
end
# Review : change in permission policy
# if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table)
if [:element_array, :element_hash].include?(table)
if credential
list = @connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid), :credential => (credential.to_s)})
else
list = @connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid)})
end
list.each do |record|
# [:execute, :rename, :move, :destroy, :create, :write, :read]
entry = {:credential => (record[:credential].to_sym), :table => (record[:element_table].to_sym), :dbid => (record[:elementid]), :permissions => {}}
[:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
if record[(the_permission)] == 1
entry[:permissions][(the_permission)] = true
else
entry[:permissions][(the_permission)] = false
end
end
result << entry
end
else
raise ArgumentError, "Invalid table specified"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid), :options => (options)}})
end
result
end
#
def revoke_element_permissions(table=:unspecified, dbid=0, credential=nil)
result = false
begin
unless self.alive?
raise Exception, "Database inactive"
end
unless self.open?
raise Exception, "Database not available"
end
unless dbid.is_a?(::Integer)
raise ArgumentError, "dbid must be specified with a Integer"
end
unless ::GxG::valid_uuid?(credential)
raise ArgumentError, "This option requires a valid credential UUID (as a String or Symbol)"
end
if table.is_a?(::Integer)
table = ::GxG::Database::Database::element_table_by_index(table)
end
# Review : change in permission policy
# if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table)
if [:element_array, :element_hash].include?(table)
@connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid), :credential => (credential.to_s)}).delete
result = true
else
raise ArgumentError, "Invalid table specified"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid), :options => (options)}})
end
result
end
#
#
def element_permissions_manifest(table=:unspecified, dbid=0, options={})
result = []
self.element_permissions(table,dbid,options).each do |entry|
the_record = {:credential => nil, :permissions => {}, :details => {}}
#
the_record[:credential] = entry[:credential]
the_record[:permissions] = entry[:permissions]
if self.role_exist?(entry[:credential])
role = self.role_fetch({:uuid => entry[:credential].to_s})
group = self.group_fetch({:uuid => role[:group_uuid].to_s})
the_record[:details][:role_title] = role[:title]
the_record[:details][:group] = group[:uuid].to_s.to_sym
the_record[:details][:group_title] = group[:title]
else
if self.user_exist?(entry[:credential])
user = self.user_fetch({:uuid => entry[:credential].to_s})
the_record[:details][:user_title] = user[:user_id]
else
log_warn("Invalid User Credential Detected: #{entry[:credential].to_s.to_sym.inspect}")
the_record[:details][:user_title] = "Invalid User Credential #{entry[:credential].to_s.to_sym.inspect} !"
end
end
result << the_record
end
result
end
#
def element_permissions_by_uuid(the_uuid=nil,options={})
result = []
begin
if self.alive?
if self.open?
found = @connector[:element_hash].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
if found
result = self.element_permissions_manifest(:element_hash,(found[:dbid]),options)
else
found = @connector[:element_array].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
if found
result = self.element_permissions_manifest(:element_array,(found[:dbid]),options)
end
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:uuid => (the_uuid)}})
end
result
end
#
def revoke_permissions_by_uuid(the_uuid=nil, credential=nil)
result = false
begin
if self.alive?
if self.open?
found = @connector[:element_hash].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
if found
result = self.revoke_element_permissions(:element_hash,(found[:dbid]),credential)
else
found = @connector[:element_array].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
if found
result = self.revoke_element_permissions(:element_array,(found[:dbid]),credential)
end
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:uuid => (the_uuid)}})
end
result
end
#
def assign_element_permission(table=:unspecified, dbid=0, credential=nil, permissions={})
result = false
begin
unless self.alive?
raise Exception, "Database inactive"
end
unless self.open?
raise Exception, "Database not available"
end
unless dbid.is_a?(::Integer)
raise ArgumentError, "dbid must be specified with a Integer"
end
unless permissions.is_a?(::Hash)
raise ArgumentError, "permissions must be specified with a Hash"
end
unless ::GxG::valid_uuid?(credential)
raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
end
if table.is_a?(::Integer)
table = ::GxG::Database::Database::element_table_by_index(table)
end
# Review : change in permission policy
# if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table)
if [:element_array, :element_hash].include?(table)
record = @connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid), :credential => (credential.to_s)}).first
if record
# update
the_update = {:execute => 0, :rename => 0, :move => 0, :destroy => 0, :create => 0, :write => 0, :read => 0}
[:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
if record[(the_permission)] == 1
the_update[(the_permission)] = 1
end
end
[:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
if permissions[(the_permission)].is_a?(::TrueClass)
the_update[(the_permission)] = 1
end
if permissions[(the_permission)].is_a?(::FalseClass)
the_update[(the_permission)] = 0
end
end
the_update[:version] = record[:version] + 0.0001
@connector[:permissions].filter({:dbid => (record[:dbid])}).update(the_update)
else
# create
new_record = {:credential => (credential.to_s), :element_table => (table.to_s), :elementid => (dbid), :execute => 0, :rename => 0, :move => 0, :destroy => 0, :create => 0, :write => 0, :read => 0}
[:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
if permissions[(the_permission)]
new_record[(the_permission)] = 1
end
end
@connector[:permissions].insert(new_record)
end
result = true
else
raise ArgumentError, "Invalid table specified"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid), :credential => (credential), :permissions => (permissions)}})
end
result
end
#
def extend_element_permissions(source_table=:unspecified, source_dbid=0, dest_table=:unspecified, dest_dbid=0)
permissions = self.element_permissions(source_table, source_dbid)
permissions.each do |entry|
if [:persisted_array, :persisted_hash].include?(dest_table)
self.assign_element_permission(dest_table, dest_dbid, entry[:credential], entry[:permissions])
end
end
end
#
def remove_element_permissions(table=:unspecified, dbid=0, options={})
result = false
begin
unless self.alive?
raise Exception, "Database inactive"
end
unless self.open?
raise Exception, "Database not available"
end
unless dbid.is_a?(::Integer)
raise ArgumentError, "dbid must be specified with a Integer"
end
unless options.is_a?(::Hash)
raise ArgumentError, "permissions must be specified with a Hash"
end
credential = options[:credential]
if credential
unless ::GxG::valid_uuid?(credential)
raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
end
end
if table.is_a?(::Integer)
table = ::GxG::Database::Database::element_table_by_index(table)
end
# Review : change in permission policy
# if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table)
if [:element_array, :element_hash].include?(table)
if credential
@connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid), :credential => (credential.to_s)}).delete
else
@connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid)}).delete
end
result = true
else
raise ArgumentError, "Invalid table specified"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid), :options => (options)}})
end
result
end
# ### Effective Permission Handling methods
# ### VFS File permission mappings
def vfs_permission_exist?(file_id=nil, credential=nil)
result = false
begin
unless self.alive?
raise Exception, "Database inactive"
end
unless self.open?
raise Exception, "Database not available"
end
unless file_id.is_a?(::String)
raise ArgumentError, "file_id must be specified with a String"
end
unless ::GxG::valid_uuid?(credential)
raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
end
#
if @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => credential.to_s}).count > 0
result = true
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}})
end
result
end
#
def vfs_permission(file_id=nil, credential=nil)
result = []
begin
unless self.alive?
raise Exception, "Database inactive"
end
unless self.open?
raise Exception, "Database not available"
end
unless file_id.is_a?(::String)
raise ArgumentError, "dbid must be specified with a String"
end
if credential
unless ::GxG::valid_uuid?(credential)
raise ArgumentError, "This option requires a valid credential UUID (as a String or Symbol)"
end
end
#
if credential
list = @connector[:file_permissions].filter({:file_id => (file_id.to_s), :credential => (credential.to_s)})
else
list = @connector[:file_permissions].filter({:file_id => (file_id.to_s)})
end
list.each do |record|
# [:execute, :rename, :move, :destroy, :create, :write, :read]
entry = {:credential => (record[:credential].to_sym), :permissions => {}}
[:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
if record[(the_permission)] == 1
entry[:permissions][(the_permission)] = true
else
entry[:permissions][(the_permission)] = false
end
end
result << entry
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}})
end
result
end
#
def vfs_permission_manifest(file_id=nil, credential=nil)
result = []
self.vfs_permission(file_id, credential).each do |entry|
the_record = {:credential => nil, :permissions => {}, :details => {}}
#
the_record[:credential] = entry[:credential]
the_record[:permissions] = entry[:permissions]
if self.role_exist?(entry[:credential])
role = self.role_fetch({:uuid => entry[:credential].to_s})
the_record[:details][:role_title] = role[:title]
else
if self.user_exist?(entry[:credential])
user = self.user_fetch({:uuid => entry[:credential].to_s})
the_record[:details][:user_title] = user[:user_id]
else
log_warn("Invalid User Credential Detected: #{entry[:credential].to_s.to_sym.inspect}")
the_record[:details][:user_title] = "Invalid User Credential #{entry[:credential].to_s.to_sym.inspect} !"
end
end
result << the_record
end
result
end
#
def effective_vfs_permission(file_id=nil, credential=nil)
result = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}
begin
unless self.alive?
raise Exception, "Database inactive"
end
unless self.open?
raise Exception, "Database not available"
end
unless file_id.is_a?(::String)
raise ArgumentError, "file_id must be specified with a String"
end
unless ::GxG::valid_uuid?(credential)
raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
end
# ### VFS Permissions
raw_permissions = []
credentials = ["00000000-0000-4000-0000-000000000000"]
if self.user_exist?(credential)
credentials << credential
role_list = self.user_roles(credential)
role_list.each do |entry|
credentials << entry[:credential]
end
#
else
if self.role_exist?(credential)
credentials << credential
else
raise ArgumentError, "Invalid credential. It is neither a user nor a role UUID"
end
end
credentials.each do |entry|
manifest = @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => (entry.to_s)})
manifest.each do |record|
new_permission = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}
[:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
if record[(the_permission)] == 1
new_permission[(the_permission)] = true
end
end
raw_permissions << new_permission
end
end
raw_permissions.each do |entry|
[:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
if entry[(the_permission)]
result[(the_permission)] = true
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}})
end
result
end
#
def create_vfs_permission(file_id=nil, credential=nil, permission={:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false})
result = false
begin
unless self.alive?
raise Exception, "Database inactive"
end
unless self.open?
raise Exception, "Database not available"
end
unless file_id.is_a?(::String)
raise ArgumentError, "file_id must be specified with a String"
end
unless permission.is_a?(::Hash)
raise ArgumentError, "permission must be specified with a Hash"
end
unless ::GxG::valid_uuid?(credential)
raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
end
#
record = @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => credential.to_s}).first
if record
update = true
dbid = record.delete(:dbid)
else
update = false
dbid = nil
record = {:file_id => file_id.to_s, :credential => credential.to_s, :execute => 0, :rename => 0, :move => 0, :destroy => 0, :create => 0, :write => 0, :read => 0}
end
permission.keys.each do |the_permission_key|
if permission[(the_permission_key)] == true
record[(the_permission_key)] = 1
else
record[(the_permission_key)] = 0
end
end
if update == true
@connector[:file_permissions].filter({:dbid => dbid}).update(record)
else
@connector[:file_permissions].insert(record)
end
result = true
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}})
end
result
end
#
def update_vfs_permission(file_id=nil, credential=nil, permission={:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false})
result = false
begin
unless self.alive?
raise Exception, "Database inactive"
end
unless self.open?
raise Exception, "Database not available"
end
unless file_id.is_a?(::String)
raise ArgumentError, "file_id must be specified with a String"
end
unless permission.is_a?(::Hash)
raise ArgumentError, "permission must be specified with a Hash"
end
unless ::GxG::valid_uuid?(credential)
raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
end
#
record = @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => credential.to_s}).first
if record
update = true
dbid = record.delete(:dbid)
else
update = false
dbid = nil
record = {:file_id => file_id.to_s, :credential => credential.to_s, :execute => 0, :rename => 0, :move => 0, :destroy => 0, :create => 0, :write => 0, :read => 0}
# Review : should we update the version ??
end
permission.keys.each do |the_permission_key|
if permission[(the_permission_key)] == true
record[(the_permission_key)] = 1
else
record[(the_permission_key)] = 0
end
end
if update == true
@connector[:file_permissions].filter({:dbid => dbid}).update(record)
else
@connector[:file_permissions].insert(record)
end
result = true
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}})
end
result
end
#
def destroy_vfs_permission(file_id=nil, credential=nil)
result = false
begin
unless self.alive?
raise Exception, "Database inactive"
end
unless self.open?
raise Exception, "Database not available"
end
unless file_id.is_a?(::String)
raise ArgumentError, "file_id must be specified with a String"
end
# ### Review : make more efficient
if credential
unless ::GxG::valid_uuid?(credential)
raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
end
record = @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => credential.to_s}).first
if record
@connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => credential.to_s}).delete
end
else
record = @connector[:file_permissions].filter({:file_id => file_id.to_s}).first
if record
@connector[:file_permissions].filter({:file_id => file_id.to_s}).delete
end
end
result = true
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}})
end
result
end
# ### DB Objects
def effective_element_permission(table=:unspecified, dbid=0, credential=nil)
result = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}
begin
unless self.alive?
raise Exception, "Database inactive"
end
unless self.open?
raise Exception, "Database not available"
end
unless dbid.is_a?(::Integer)
raise ArgumentError, "dbid must be specified with a Integer"
end
unless ::GxG::valid_uuid?(credential)
raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
end
if table.is_a?(::Integer)
table = ::GxG::Database::Database::element_table_by_index(table)
end
# Review : change in permission policy
# if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table)
if [:element_array, :element_hash].include?(table)
# is credential a user or role?
raw_permissions = []
# Include PUBLIC credential
credentials = ["00000000-0000-4000-0000-000000000000"]
#
if self.user_exist?(credential)
credentials << credential
role_list = self.user_roles(credential)
role_list.each do |entry|
credentials << entry[:credential]
end
#
else
if self.role_exist?(credential)
credentials << credential
else
raise ArgumentError, "Invalid credential. It is neither a user nor a role UUID"
end
end
credentials.each do |entry|
manifest = @connector[:permissions].filter({:credential => (entry.to_s), :element_table => (table.to_s), :elementid => (dbid)})
manifest.each do |record|
new_permission = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}
[:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
if record[(the_permission)] == 1
new_permission[(the_permission)] = true
end
end
raw_permissions << new_permission
end
end
permission = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}
raw_permissions.each do |entry|
[:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
if entry[(the_permission)]
permission[(the_permission)] = true
end
end
end
db_limits = self.db_permissions()
[:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
if db_limits[(the_permission)] and permission[(the_permission)]
result[(the_permission)] = true
end
end
#
else
raise ArgumentError, "Invalid table specified"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid), :credential => (credential)}})
end
result
end
#
def effective_uuid_permission(uuid=nil, credential=nil)
result = nil
begin
if self.alive?
if self.open?
address = nil
record = @connector[:element_hash].filter({:uuid => (uuid.to_s)}).first
if record
address = {:database => self, :table => :element_hash, :dbid => (record[:dbid])}
else
record = @connector[:element_array].filter({:uuid => (uuid.to_s)}).first
if record
address = {:database => self, :table => :element_array, :dbid => (record[:dbid])}
end
end
if address
result = self.effective_element_permission(address[:table], address[:dbid], credential)
else
# ### Review : Experimental Code - attempt to seemlessly integrate dbs of different roles into in-memory structures.
already_checked = [(self)]
::GxG::DB[:roles].each_pair do |the_db_role, the_database|
unless already_checked.include?(the_database)
record = the_database.connector[:element_hash].filter({:uuid => (uuid.to_s)}).first
if record
address = {:database => the_database, :table => :element_hash, :dbid => (record[:dbid])}
result = the_database.effective_element_permission(address[:table], address[:dbid], credential)
break
else
record = the_database.connector[:element_array].filter({:uuid => (uuid.to_s)}).first
if record
address = {:database => the_database, :table => :element_array, :dbid => (record[:dbid])}
result = the_database.effective_element_permission(address[:table], address[:dbid], credential)
break
end
end
already_checked << the_database
end
end
#
end
else
raise Exception, "Database not available"
end
else
raise Exception, "Database not available (defunct)"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:uuid => (uuid), :credential => (credential)}})
end
result
end
# ### Data Handling methods
#
def element_exists?(the_table=:unspecified, the_dbid=0)
result = false
if the_table.is_a?(::Integer)
the_table = ::GxG::Database::Database::element_table_by_index(the_table)
end
if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(the_table)
begin
record = @connector[(the_table)].select(:dbid).where({:dbid => (the_dbid)}).first
if record
result = true
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:table => (the_table), :dbid => (the_dbid)}})
end
end
result
end
#
def element_retrieve(the_table=:unspecified, the_filter={}, options={})
result = []
begin
if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :text_page, :element_binary,:binary_page, :element_array, :element_hash].include?(the_table)
#
if the_filter.is_a?(::Hash)
if options[:order]
raw_result = @connector[(the_table)].filter(the_filter).order(options[:order])
else
raw_result = @connector[(the_table)].filter(the_filter)
end
# Review : UTF-8 Encoding Error Fix
if options[:single_record] == true
result = raw_result.first
# utf8 fix
if the_table == :text_page
if result[:content].is_a?(::String)
if result[:content].base64?
result[:content] = result[:content].decode64
end
end
end
#
else
# utf8 fix
raw_result.each do |the_record|
if the_record[:content].is_a?(::String)
if the_record[:content].base64?
the_record[:content] = the_record[:content].decode64
end
end
result << the_record
end
# result = raw_result
end
#
else
raise ArguementError, "You MUST provide a record selection filter as a Hash. "
end
else
raise ArgumentError, "The table specified is unknown - Check your code."
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:table => the_table, :filter => the_filter, :options => options}})
end
result
end
#
def element_create(the_table=:unspecified, the_record=nil)
# Review : rewrite - in use
result = nil
begin
unless the_table.is_a?(::Symbol)
raise ArgumentError, "You MUST provide the table specifier as a Symbol."
end
unless the_record.is_a?(::Hash)
raise ArgumentError, "You MUST provide a record in the form of a Hash."
end
if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :text_page, :element_binary, :binary_page, :element_array, :element_hash].include?(the_table)
# Review : UTF8 fix
begin
result = @connector[(the_table)].insert(the_record)
rescue Exception
if the_table == :text_page
the_record[:content] = the_record[:content].encode64
end
result = @connector[(the_table)].insert(the_record)
end
else
raise ArgumentError, "The table specified is unknown - Check your code."
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:table => (the_table), :record => (the_record)}})
end
result
end
#
def element_update(the_table=:unspecified, the_filter={}, the_record=nil)
result = false
begin
unless the_table.is_a?(::Symbol)
raise ArgumentError, "You MUST provide the table specifier as a Symbol."
end
unless the_filter.is_a?(::Hash)
raise ArgumentError, "You MUST provide a selection filter in the form of a Hash."
end
unless the_record.is_a?(::Hash)
raise ArgumentError, "You MUST provide a record in the form of a Hash."
end
if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :text_page, :element_binary, :binary_page, :element_array, :element_hash].include?(the_table)
# Review : shim to eliminate UTF-8 encoding errors
begin
@connector[(the_table)].filter(the_filter).update(the_record)
rescue Exception
if the_table == :text_page
the_record[:content] = the_record[:content].encode64
end
@connector[(the_table)].filter(the_filter).update(the_record)
end
result = true
else
raise ArgumentError, "The table specified is unknown - Check your code."
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:table => the_table, :filter => the_filter, :record => the_record}})
end
result
end
#
def structure_detach(the_uuid=nil)
if ::GxG::valid_uuid?(the_uuid)
record = self.structural_link(the_uuid)
if record
record[:database].connector()[(record[:table])].filter({:dbid => record[:dbid]}).update({:element => "element_boolean", :element_boolean => -1, :element_hash_uuid => "", :element_array_uuid => ""})
true
else
false
end
else
false
end
end
#
def structural_link(the_uuid=nil)
# Review : rewrite - in use
result = nil
if ::GxG::valid_uuid?(the_uuid)
search_queue = [(self)]
::GxG::DB[:roles].each_pair do |the_role, the_database|
unless search_queue.include?(the_database)
search_queue << the_database
end
end
search_queue.each do |the_database|
found = the_database.connector()[:hash_properties].filter({:element_hash_uuid => the_uuid.to_s}).first
if found
result = {:database => the_database, :table => :hash_properties, :dbid => found[:dbid]}
break
end
found = the_database.connector()[:hash_properties].filter({:element_array_uuid => the_uuid.to_s}).first
if found
result = {:database => the_database, :table => :hash_properties, :dbid => found[:dbid]}
break
end
found = the_database.connector()[:array_elements].filter({:element_hash_uuid => the_uuid.to_s}).first
if found
result = {:database => the_database, :table => :hash_properties, :dbid => found[:dbid]}
break
end
found = the_database.connector()[:array_elements].filter({:element_array_uuid => the_uuid.to_s}).first
if found
result = {:database => the_database, :table => :hash_properties, :dbid => found[:dbid]}
break
end
end
end
result
end
#
def structure_attached?(the_uuid=nil)
# Review : rewrite - in use
result = false
record = self.structural_link(the_uuid)
if record
result = true
end
result
end
#
def element_in_use?(db_table=:unspecified, dbid = 0)
# Review : rewrite - in use for now.
# Review : rewrite - todo (alter to reflect the new architecture : perhaps limit to structures only)
unless db_table.is_any?(::Integer, ::Symbol)
raise ArgumentError, "Table needs to be specified as a Symbol, or a table code Integer"
end
unless dbid.is_a?(::Integer)
raise ArgumentError, "Record ID needs to be specified with an Integer"
end
result = false
begin
if db_table == :element_hash || db_table == :element_array
# Review : make more efficient in later revs of this.
the_record = @connector[(db_table)].filter({:dbid => (dbid)}).select(:uuid).first
if the_record
if @connector[:hash_properties].filter({:element_hash_uuid => the_record[:uuid]}).count > 0 || @connector[:array_elements].filter({:element_array_uuid => the_record[:uuid]}).count > 0
result = true
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:table_code => (db_table), :dbid => (dbid)}})
end
result
end
#
def element_parent_address(the_table=:unspecified, the_dbid=0)
result = nil
if self.element_in_use?(the_table,the_dbid)
begin
link = @connector[:hash_links].select(:parent_uuid).where({:element => (::GxG::Database::Database::element_table_index(the_table)), :elementid => (the_dbid)}).first
if link
record = @connector[:element_hash].select(:dbid).where({:uuid => link[:parent_uuid]}).first
if record
result = {:table => :element_hash, :dbid => (record[:dbid])}
end
else
link = @connector[:array_links].select(:parent_uuid).where({:element => (::GxG::Database::Database::element_table_index(the_table)), :elementid => (the_dbid)}).first
if link
record = @connector[:element_array].select(:dbid).where({:uuid => link[:parent_uuid]}).first
if record
result = {:table => :element_array, :dbid => (record[:dbid])}
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:table => (the_table), :dbid => (the_dbid)}})
end
end
result
end
#
def element_manifest(the_table=:unspecified, the_dbid=0, everything=false)
# Review : rewrite - in use
# returns an Array of tables and dbid pairs and sub-item pairs if they exist
result = []
#
begin
unless the_dbid > 0
raise ArgumentError, "dbid specified must be greater than zero"
end
case the_table
when :element_text
the_record = @connector[:element_text].select(:uuid, :dbid).where({:dbid => (the_dbid)}).first
if the_record
result << {:table => :element_text, :dbid => the_record[:dbid]}
if everything
the_pages = @connector[:text_page].select(:dbid, :ordinal).where({:parent_uuid => (the_record[:uuid])}).order(:ordinal)
the_pages.each do |entry|
result << {:table => :text_page, :dbid => entry[:dbid]}
end
end
end
when :element_binary
the_record = @connector[:element_binary].select(:uuid, :dbid).where({:dbid => (the_dbid)}).first
if the_record
result << {:table => :element_binary, :dbid => the_record[:dbid]}
if everything
the_pages = @connector[:binary_page].select(:dbid, :ordinal).where({:parent_uuid => (the_record[:uuid])}).order(:ordinal)
the_pages.each do |entry|
result << {:table => :binary_page, :dbid => entry[:dbid]}
end
end
end
when :element_array, :element_hash
record_db = [{:table => the_table, :dbid => the_dbid}]
while record_db.size > 0 do
item = record_db.shift
case item[:table]
when :element_text
the_record = @connector[:element_text].select(:uuid, :dbid).where({:dbid => item[:dbid]}).first
if the_record
result << {:table => :element_text, :dbid => the_record[:dbid]}
if everything
the_pages = @connector[:text_page].select(:dbid, :ordinal).where({:parent_uuid => (the_record[:uuid])}).order(:ordinal)
the_pages.each do |entry|
result << {:table => :text_page, :dbid => entry[:dbid]}
end
end
end
when :element_binary
the_record = @connector[:element_binary].select(:uuid, :dbid).where({:dbid => item[:dbid]}).first
if the_record
result << {:table => :element_binary, :dbid => the_record[:dbid]}
if everything
the_pages = @connector[:binary_page].select(:dbid, :ordinal).where({:parent_uuid => (the_record[:uuid])}).order(:ordinal)
the_pages.each do |entry|
result << {:table => :binary_page, :dbid => entry[:dbid]}
end
end
end
when :element_array
result << {:table => item[:table], :dbid => item[:dbid]}
array_record = @connector[:element_array].select(:uuid).where({:dbid => item[:dbid]}).first
if array_record
@connector[:array_elements].filter({:parent_uuid => (array_record[:uuid])}).order(:ordinal).each do |entry|
if entry[:element_text_uuid].size > 0
the_temp_record = @connector[:element_text].select(:dbid).where({:uuid => entry[:element_text_uuid]}).first
if the_temp_record
record_db << {:table => :element_text, :dbid => the_temp_record[:dbid]}
end
end
if entry[:element_binary_uuid].size > 0
the_temp_record = @connector[:element_binary].select(:dbid).where({:uuid => entry[:element_binary_uuid]}).first
if the_temp_record
record_db << {:table => :element_binary, :dbid => the_temp_record[:dbid]}
end
end
if entry[:element_array_uuid].size > 0
the_temp_record = @connector[:element_array].select(:dbid).where({:uuid => entry[:element_array_uuid]}).first
if the_temp_record
record_db << {:table => :element_array, :dbid => the_temp_record[:dbid]}
end
end
if entry[:element_hash_uuid].size > 0
the_temp_record = @connector[:element_hash].select(:dbid).where({:uuid => entry[:element_hash_uuid]}).first
if the_temp_record
record_db << {:table => :element_hash, :dbid => the_temp_record[:dbid]}
end
end
result << {:table => :array_elements, :dbid => entry[:dbid]}
end
end
when :element_hash
result << {:table => item[:table], :dbid => item[:dbid]}
hash_record = @connector[:element_hash].select(:uuid).where({:dbid => item[:dbid]}).first
if hash_record
@connector[:hash_properties].filter({:parent_uuid => (hash_record[:uuid])}).order(:ordinal).each do |entry|
if entry[:element_text_uuid].size > 0
the_temp_record = @connector[:element_text].select(:dbid).where({:uuid => entry[:element_text_uuid]}).first
if the_temp_record
record_db << {:table => :element_text, :dbid => the_temp_record[:dbid]}
end
end
if entry[:element_binary_uuid].size > 0
the_temp_record = @connector[:element_binary].select(:dbid).where({:uuid => entry[:element_binary_uuid]}).first
if the_temp_record
record_db << {:table => :element_binary, :dbid => the_temp_record[:dbid]}
end
end
if entry[:element_array_uuid].size > 0
the_temp_record = @connector[:element_array].select(:dbid).where({:uuid => entry[:element_array_uuid]}).first
if the_temp_record
record_db << {:table => :element_array, :dbid => the_temp_record[:dbid]}
end
end
if entry[:element_hash_uuid].size > 0
the_temp_record = @connector[:element_hash].select(:dbid).where({:uuid => entry[:element_hash_uuid]}).first
if the_temp_record
record_db << {:table => :element_hash, :dbid => the_temp_record[:dbid]}
end
end
result << {:table => :hash_properties, :dbid => entry[:dbid]}
end
end
else
the_record = @connector[(item[:table])].select(:dbid).where({:dbid => (item[:dbid])}).first
if the_record
result << {:table => item[:table], :dbid => item[:dbid]}
end
end
end
#
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:table => (the_table), :dbid => (the_dbid)}})
end
result
end
#
def element_destroy(credential=nil,table=:unspecified,dbid=0, options={})
# Review : rewrite for new arch.
# Review : rewrite - in use
result = false
begin
if self.alive?
if self.open?
if self.db_permissions()[:write]
# if credential has :destroy permission on this element?
if ::GxG::valid_uuid?(credential)
if [:persisted_array, :persisted_hash].include?(table.to_s.to_sym)
go_ahead = self.effective_element_permission(table, dbid, credential)[:destroy]
else
go_ahead = true
end
# xxx Quick fix : BETA: clean all this up!
if go_ahead
# Review : DB overhaul / rewrite - allow delete if object has write lock in place? I think not. (return false)
# Note: BETA - bring back read-locks? it was 9x slower with them ... not sure.
elements = self.element_manifest(table, dbid, false)
elements.each do |entry|
#
unless self.element_in_trash?(entry[:table], entry[:dbid])
@connector[:trash].insert({:element_table => entry[:table].to_s, :elementid => entry[:dbid]})
end
#
#if @connector[:element_locks].filter({:type => "read", :element_table => entry[:table].to_s, :elementid => entry[:dbid]}).count > 0
# put in trash
#unless self.element_in_trash?(entry[:table], entry[:dbid])
#@connector[:trash].insert({:element_table => entry[:table].to_s, :elementid => entry[:dbid]})
#end
#else
# delete now
#manifest = self.element_manifest(entry[:table], entry[:dbid], true)
#manifest.each do |item|
#@connector[:permissions].filter({:element_table => (item[:table].to_s), :elementid => (item[:dbid])}).delete
#@connector[(item[:table])].filter({:dbid => item[:dbid]}).delete
#end
#end
end
result = true
else
raise Exception, "You do not have permission to destory this element"
end
else
raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)"
end
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:credential => (credential), :table => (table), :dbid => (dbid)}})
end
result
end
#
def destroy_by_uuid(credential=nil, the_uuid=nil)
# Review : rewrite - in use
# element_destroy(credential=nil,table=:unspecified,dbid=0, options={})
# .effective_uuid_permission(data["location"].to_s.to_sym,@credential)
result = false
begin
if self.alive?
if self.open?
if self.db_permissions()[:destroy] == true
if ::GxG::valid_uuid?(credential)
found = @connector[:element_hash].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
if found
self.element_destroy(credential, :element_hash,(found[:dbid]))
result = true
else
found = @connector[:element_array].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
if found
self.element_destroy(credential, :element_array,(found[:dbid]))
result = true
end
end
else
raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)"
end
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:credential => (credential), :uuid => (the_uuid)}})
end
result
end
#
def element_byte_size(table=:unspecified, dbid=0)
#
result = 0
begin
if self.alive?
if self.open?
migration = self.db_migration()
elements = self.element_manifest(table, dbid, true)
elements.each do |entry|
table_definition = migration[(entry[:table])]
if table_definition
table_definition.keys.each do |the_field|
result += table_definition[(the_field)][:bytes].to_i
end
end
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid)}})
end
result
end
#
def byte_size_by_uuid(the_uuid=nil)
result = 0
begin
if self.alive?
if self.open?
found = @connector[:element_hash].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
if found
result += self.element_byte_size(:element_hash,(found[:dbid]))
else
found = @connector[:element_array].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
if found
result += self.element_byte_size(:element_array,(found[:dbid]))
end
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:uuid => (the_uuid)}})
end
result
end
#
def persistable?(the_object)
result = true
unless the_object.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
if the_object.is_any?(::Hash, ::Array)
the_object.search do |item, selector, container|
if result
if ::GxG::Database::Database::element_table_for_instance(item) == :unspecified
result = false
break
end
end
nil
end
else
if ::GxG::Database::Database::element_table_for_instance(the_object) == :unspecified
result = false
end
end
end
result
end
#
def iterative_persist(old_root=nil, credential=nil, options={})
# New PersistedArray, or PersistedHash interface:
# mount element: {:database => <database>, :uuid => <uuid>, :credential => <uuid>, :delegate => nil/<uuid>, :parent => nil/<an-object>}
# create element: {:database => <database>, :credential => <uuid>, :delegate => nil/<uuid>, :parent => nil/<an-object>}
#
result = nil
begin
# db open?
# write permissions on db?
unless ::GxG::valid_uuid?(credential)
raise ArgumentError, "You MUST provide a valid UUID as a credential."
end
unless old_root.is_any?(::Array, ::Hash)
raise ArgumentError, "You MUST provide either an Array or a Hash."
end
unless self.persistable?(old_root)
raise ArgumentError, "The object you are attempting to persist contains a non-persistable item."
end
if old_root.is_a?(::Array)
original_partner = ::GxG::Database::PersistedArray.new({:database => self, :credential => credential}, options)
original_partner.get_reservation()
self.enforce_permission_policy({:action => :extend, :credential => credential, :source => original_partner, :destination => original_partner})
#
end
if old_root.is_a?(::Hash)
original_partner = ::GxG::Database::PersistedHash.new({:database => self, :credential => credential}, options)
original_partner.get_reservation()
self.enforce_permission_policy({:action => :extend, :credential => credential, :source => original_partner, :destination => original_partner})
end
if old_root.is_any?(::GxG::Database::PersistedArray, ::GxG::Database::PersistedHash)
# TODO: support subsuming already persisted objects.
end
paring_data = [{:parent => nil, :parent_selector => nil, :object => old_root, :partner => original_partner}]
children_of = Proc.new do |the_parent=nil|
list = []
paring_data.each do |node|
if node[:parent].object_id == the_parent.object_id
list << node
end
end
list
end
#
parent_of = Proc.new do |the_parent=nil|
output = nil
paring_data.each do |entry|
if entry[:object].object_id == the_parent.object_id
output = entry
end
end
output
end
find_partner = Proc.new do |the_object|
found_partner = nil
paring_data.each do |the_record|
if the_record.is_a?(::Hash)
if the_record[:object].object_id == the_object.object_id
found_partner = the_record[:partner]
break
end
end
end
found_partner
end
# build paring data:
delegate_permission = nil
old_root.search do |the_value, the_selector, the_container|
if the_value.is_a?(::Array)
paring_data << {:parent => the_container, :parent_selector => the_selector, :object => the_value, :partner => ::GxG::Database::PersistedArray.new({:database => self, :credential => credential})}
else
if the_value.is_a?(::Hash)
paring_data << {:parent => the_container, :parent_selector => the_selector, :object => the_value, :partner => ::GxG::Database::PersistedHash.new({:database => self, :credential => credential})}
else
paring_data << {:parent => the_container, :parent_selector => the_selector, :object => the_value, :partner => the_value}
end
end
end
# Assign objects to structure in order by parent / parent_selector
link_db = [(paring_data[0])]
while link_db.size > 0
entry = link_db.shift
if entry.is_a?(::Hash)
if entry[:object].is_any?(::Array, ::Hash)
# get children and assign
children = children_of.call(entry[:object])
children.each do |child|
entry[:partner][(child[:parent_selector])] = child[:partner]
if child[:partner].is_any?(::GxG::Database::PersistedArray, ::GxG::Database::PersistedHash)
link_db << child
end
end
end
end
end
result = original_partner
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:object => old_root, :credential => credential}})
end
result
end
#
def try_persist(the_object=nil, credential=nil, options={})
result = nil
if the_object.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
result = the_object
else
begin
unless ::GxG::valid_uuid?(credential)
raise ArgumentError, "You must supply a credential (36 character limit) as a String or Symbol"
end
unless options.is_a?(::Hash)
raise ArgumentError, "You must supply persisting options as a Hash"
end
table = ::GxG::Database::Database::element_table_for_instance(the_object)
if table == :unspecified
raise ArgumentError, "You provided an object that cannot be persisted."
end
if [:element_hash, :element_array].include?(table)
result = self.iterative_persist(the_object, credential, options)
else
result = the_object
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:object => the_object, :credential => credential, :options => options}})
end
end
result
end
#
def retrieve_by_address(the_address={}, credential=nil, delegate=nil)
result = nil
begin
unless ::GxG::valid_uuid?(credential)
raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)"
end
if self.alive?
if self.open?
address = {}
if the_address[:database]
unless the_address[:database] == self
raise ArgumentError, "Database mismatch. Call this method on database #{the_address[:database].inspect}"
end
address[:database] = the_address[:database]
else
address[:database] = self
end
unless ((the_address[:table]) and (the_address[:dbid].is_a?(::Integer)))
raise ArgumentError, "Invalid database address: #{the_address.inspect}"
end
if the_address[:table].is_a?(::Integer)
address[:table] = ::GxG::Database::Database::element_table_by_index(the_address[:table])
else
address[:table] = the_address[:table]
end
address[:dbid] = the_address[:dbid]
unless self.element_in_trash?(address[:table], address[:dbid])
if [:persisted_array, :persisted_hash].include?(address[:table])
the_permission = address[:database].effective_element_permission(address[:table], address[:dbid], credential)
else
the_permission = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => true}
end
# Review ????
# puts "Effective Permission: #{the_permission.inspect} for #{address[:table].inspect},#{address[:dbid].inspect}"
if the_permission[:read] == true
case address[:table]
when :element_array
record = address[:database].connector()[(address[:table].to_s.to_sym)].select(:uuid).where({:dbid => (address[:dbid])}).first
if record
result = ::GxG::Database::PersistedArray.new({:database => (address[:database]), :uuid => (record[:uuid].to_sym), :credential => (credential), :delegate => (delegate)})
else
raise Exception, "Unable to retrieve PersistedArray"
end
when :element_hash
record = address[:database].connector()[:element_hash].select(:uuid, :format).where({:dbid => (address[:dbid])}).first
if record
result = ::GxG::Database::PersistedHash.new({:database => (address[:database]), :uuid => (record[:uuid].to_sym), :credential => (credential), :delegate => (delegate)})
if result
if record[:format].to_s.size > 0
result.set_format(record[:format].to_sym)
end
end
else
raise Exception, "Unable to retrieve PersistedHash"
end
else
raise Exception, "Invalid table specifier: #{address[:table].inspect}"
end
else
# At this point the idea is that if you don't have permissions to read it, you never see it
# raise Exception, "You don't have permission to retrieve this element"
end
#
end
#
else
raise Exception, "Database not available"
end
else
raise Exception, "Database not available (defunct)"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:address => (the_address), :credential => (credential), :delegate => (delegate)}})
end
result
end
#
def retrieve_by_uuid(uuid=nil, credential=nil, delegate=nil)
result = nil
begin
if self.alive?
if self.open?
address = nil
record = @connector[:element_hash].filter({:uuid => (uuid.to_s)}).first
if record
address = {:database => self, :table => :element_hash, :dbid => (record[:dbid])}
else
record = @connector[:element_array].filter({:uuid => (uuid.to_s)}).first
if record
address = {:database => self, :table => :element_array, :dbid => (record[:dbid])}
end
end
if address
result = self.retrieve_by_address(address, credential, delegate)
else
# ### Review : Experimental Code - attempt to seemlessly integrate dbs of different roles into in-memory structures.
already_checked = [(self)]
::GxG::DB[:roles].each_pair do |the_db_role, the_database|
unless already_checked.include?(the_database)
record = the_database.connector[:element_hash].filter({:uuid => (uuid.to_s)}).first
if record
address = {:database => the_database, :table => :element_hash, :dbid => (record[:dbid])}
result = the_database.retrieve_by_address(address, credential, delegate)
break
else
record = the_database.connector[:element_array].filter({:uuid => (uuid.to_s)}).first
if record
address = {:database => the_database, :table => :element_array, :dbid => (record[:dbid])}
result = the_database.retrieve_by_address(address, credential, delegate)
break
end
end
already_checked << the_database
end
end
#
end
else
raise Exception, "Database not available"
end
else
raise Exception, "Database not available (defunct)"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:uuid => (uuid), :credential => (credential), :delegate => (delegate)}})
end
result
end
# Element Write-Lock Handling methods
def reserve_element_locks(credential=nil, manifest=[], type=:read)
result = nil
begin
if self.alive?
if self.open?
unless ::GxG::valid_uuid?(credential)
raise ArgumentError, "You must supply a valid credential UUID to reserve locks"
end
unless manifest.is_a?(::Array)
raise ArguementError, "You must supply an Array of tables and record ids to reserve for writing"
end
unless [:write, :read].include?(type)
raise ArgumentError, "You must specify a type of reservation: either :write or :read are supported"
end
if manifest.size > 0
reservation_uuid = ::GxG::uuid_generate()
while @connector[:element_locks].filter({:reservation => (reservation_uuid.to_s)}).count > 0
reservation_uuid = ::GxG::uuid_generate()
end
if type == :write
if self.db_permissions()[:write]
# Write-lock must be unique to the elements
manifest.each do |entry|
if @connector[:element_locks].filter({:type => (type.to_s), :element_table => entry[:table].to_s, :elementid => entry[:dbid]}).count > 0
self.release_element_locks(reservation_uuid)
# raise Exception, "A reservation already exists for element #{entry[:table]}:#{entry[:dbid]}"
# Note - just silently fail and return Nil - no reservation for you.
break
else
@connector[:element_locks].insert({:type => (type.to_s), :reservation => reservation_uuid.to_s, :credential => credential.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]})
end
end
# if write-reservation was made
if @connector[:element_locks].filter({:reservation => reservation_uuid.to_s}).count > 0
result = reservation_uuid.to_sym
end
end
else
# Read-lock reservation - can share elements
if self.db_permissions()[:write]
manifest.each do |entry|
@connector[:element_locks].insert({:type => (type.to_s), :reservation => reservation_uuid.to_s, :credential => credential.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]})
#
end
end
# Note - on a read-only db it will act as though it has a read-reservation since no deletions can be made.
result = reservation_uuid.to_sym
end
end
#
else
raise Exception, "Database not available"
end
else
raise Exception, "Database not available (defunct)"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:credential => (credential), :manifest => (manifest), :type => (type)}})
end
result
end
#
def release_element_locks(reservation_uuid=nil)
result = false
begin
if self.alive?
if self.open?
unless ::GxG::valid_uuid?(reservation_uuid)
raise ArgumentError, "You must supply a valid reservation UUID (as a String or Symbol) to release locks"
end
if self.db_permissions()[:write]
@connector[:element_locks].filter({:reservation => reservation_uuid.to_s}).delete
end
# on read-only db, no reservation was made so mission accomplished
result = true
else
raise Exception, "Database not available"
end
else
raise Exception, "Database not available (defunct)"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:reservation => (reservation_uuid)}})
end
result
end
#
def reservation_add_element(reservation_uuid=nil, table=:unspecified, dbid=0)
result = false
#
begin
if self.alive?
if self.open?
unless ::GxG::valid_uuid?(reservation_uuid)
raise ArgumentError, "You must supply a valid reservation UUID (as a String or Symbol) to add an element to the reserve locks"
end
record = @connector[:element_locks].select(:type, :reservation, :credential).where({:reservation => reservation_uuid.to_s}).first
if record
type = record[:type].to_sym
credential = record[:credential].to_sym
manifest = self.element_manifest(table, dbid, false)
if type == :write
if self.db_permissions()[:write]
#
manifest.each do |entry|
if @connector[:element_locks].filter({:type => (type.to_s), :reservation => reservation_uuid.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]}).count == 0
@connector[:element_locks].insert({:type => (type.to_s), :reservation => reservation_uuid.to_s, :credential => credential.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]})
end
end
result = true
end
else
# read
if self.db_permissions()[:write]
#
manifest.each do |entry|
@connector[:element_locks].insert({:type => (type.to_s), :reservation => reservation_uuid.to_s, :credential => credential.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]})
end
result = true
end
end
end
else
raise Exception, "Database not available"
end
else
raise Exception, "Database not available (defunct)"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:reservation => (reservation_uuid)}})
end
#
result
end
#
def reservation_remove_element(reservation_uuid=nil, table=:unspecified, dbid=0)
result = false
#
begin
if self.alive?
if self.open?
unless ::GxG::valid_uuid?(reservation_uuid)
raise ArgumentError, "You must supply a valid reservation UUID (as a String or Symbol) to remove element from reserve locks"
end
record = @connector[:element_locks].select(:reservation).where({:reservation => reservation_uuid.to_s}).first
if record
manifest = self.element_manifest(table, dbid, false)
if self.db_permissions()[:write]
#
manifest.each do |entry|
if @connector[:element_locks].filter({:reservation => reservation_uuid.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]}).count > 0
@connector[:element_locks].filter({:reservation => reservation_uuid.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]}).delete
end
end
result = true
end
end
else
raise Exception, "Database not available"
end
else
raise Exception, "Database not available (defunct)"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:reservation => (reservation_uuid)}})
end
#
result
end
#
def empty_trash()
result = false
begin
if self.alive?
if self.open?
if self.db_permissions()[:write]
scrub_list = []
@connector[:trash].filter({}).each do |entry|
the_table = entry[:element_table].to_sym
the_dbid = entry[:elementid]
if @connector[:element_locks].filter({:type => "write", :element_table => the_table.to_s, :elementid => the_dbid}).count == 0
# delete full manifest
manifest = self.element_manifest(the_table, the_dbid, true)
manifest.each do |item|
@connector[:permissions].filter({:element_table => (item[:table].to_s), :elementid => (item[:dbid])}).delete
@connector[(item[:table])].filter({:dbid => item[:dbid]}).delete
end
scrub_list << entry[:dbid]
end
end
scrub_list.each do |entry|
@connector[:trash].filter({:dbid => entry}).delete
end
if @connector[:trash].filter({}).count == 0
result = true
end
end
end
end
rescue Exception => the_error
log_error({:error => the_error})
end
result
end
#
def element_in_trash?(table=:unspecified, dbid=0)
result = false
begin
if self.alive?
if self.open?
if @connector[:trash].filter({:element_table => table.to_s, :elementid => dbid}).count > 0
result = true
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid)}})
end
result
end
#
def element_recover(table=:unspecified, dbid=0)
# recover an element from the trash
result = false
begin
if self.alive?
if self.open?
if self.db_permissions()[:write]
unless [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table)
raise ArgumentError, "Invalid table specified"
end
unless dbid > 0
raise ArgumentError, "dbid must be greater than zero"
end
if self.element_in_trash?(table, dbid)
scrub_list = []
manifest = self.element_manifest(table, dbid)
manifest.each do |entry|
if @connector[:trash].filter({:element_table => (entry[:table].to_s), :elementid => (entry[:dbid])}).count > 0
record = @connector[:trash].select(:dbid).where({:element_table => (entry[:table].to_s), :elementid => (entry[:dbid])}).first
if record
scrub_list << record[:dbid]
end
end
end
scrub_list.each do |entry|
@connector[:trash].filter({:dbid => entry}).delete
end
result = true
else
# it was never in the trash, so mission accomplished
result = true
end
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid)}})
end
result
end
# ### Format handing methods
def format_template(type=:structure)
result = nil
begin
if self.alive?
if self.open?
unless type.is_any?(::String, ::Symbol)
raise ArgumentError, "You must supply a type as a String or Symbol: :structure or :binary"
end
the_uuid = ::GxG::uuid_generate()
while (@connector[:formats].filter({:uuid => (the_uuid)}).count > 0 or @connector[:element_text].filter({:uuid => (the_uuid)}).count > 0)
the_uuid = ::GxG::uuid_generate()
end
result = {:uuid => (the_uuid.to_sym), :type => (type.to_s), :ufs => "unspecified", :title => "", :version => BigDecimal(0.0), :mime_types => [], :content => nil}
case type
when :structure
result[:content] = {}
when :binary
result[:content] = {:sample_size => 0, :recognition => "", :import => "", :export => ""}
else
raise Exception, "Invalid format type specified: #{type.inspect}. Use :structure or :binary"
end
else
raise Exception, "Database not available"
end
else
raise Exception, "Attempted to access a defunct Database"
end
rescue Exception => the_error
result = nil
log_error({:error => the_error, :parameters => {:type => (type)}})
end
result
end
#
def format_list(search_options={})
result = []
begin
if self.alive?
if self.open?
unless search_options.is_a?(::Hash)
raise ArgumentError, "You must specify search options as a Hash"
end
the_filter = {}
if search_options[:uuid]
the_filter[:uuid] = search_options[:uuid].to_s
end
if search_options[:type]
the_filter[:type] = search_options[:type].to_s
end
if search_options[:ufs]
the_filter[:ufs] = search_options[:ufs].to_s[0..4096]
end
if search_options[:title]
the_filter[:title] = search_options[:title].to_s[0..256]
end
if search_options[:version].is_a?(::Numeric)
the_filter[:version] = ::BigDecimal.new(search_options[:version].to_s)
end
mime_type = search_options[:mime_type].to_s
if mime_type.size > 0
@connector[:formats].filter(the_filter).where(::Sequel.like(:mime_types, ("%" + mime_type + "%"))).order(::Sequel[:version].desc).each do |entry|
result << {:uuid => entry[:uuid].to_sym, :type => entry[:type].to_sym, :ufs => entry[:ufs].to_sym, :title => entry[:title], :version => entry[:version]}
end
else
@connector[:formats].filter(the_filter).order(::Sequel[:version].desc).each do |entry|
result << {:uuid => entry[:uuid].to_sym, :type => entry[:type].to_sym, :ufs => entry[:ufs].to_sym, :title => entry[:title], :version => entry[:version]}
end
end
else
raise Exception, "Database not available"
end
else
raise Exception, "Attempted to access a defunct Database"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:search_options => (search_options)}})
end
result
end
#
def format_load(search_options={})
result = nil
begin
if self.alive?()
if self.open?
format_stub = self.format_list(search_options).first
if format_stub
format_record = @connector[:formats].filter({:uuid => (format_stub[:uuid].to_s)}).first
if format_record
content = ""
@connector[:text_page].filter({:parent_uuid => (format_stub[:uuid].to_s)}).order(:ordinal).each do |entry|
content << entry[:content]
end
format_record.delete(:dbid)
format_record[:ufs] = format_stub[:ufs]
format_record[:type] = format_stub[:type]
if content.size > 0
if content.base64?
content = content.decode64
end
if content.json?
content = ::JSON::parse(content.to_s, {:symbolize_names => true})
end
if content[:type]
content = ::Hash.gxg_import(content)
end
format_record[:content] = content
else
format_record[:content] = nil
end
#
if format_record[:mime_types].size > 0
if format_record[:mime_types].include?(",")
format_record[:mime_types] = format_record[:mime_types].split(",")
else
format_record[:mime_types] = [(format_record[:mime_types])]
end
end
#
result = format_record
end
end
else
raise Exception, "Database not available"
end
else
raise Exception, "Attempted to access a defunct Database"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:search_options => (search_options)}})
end
result
end
#
def new_structure_from_format(credential=nil, search_options={})
result = nil
begin
if self.alive?()
if self.open?
unless ::GxG::valid_uuid?(credential)
raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)"
end
format = self.format_load(search_options)
if format
# TODO: add support for :binary formats.
unless format[:type] == :structure
raise Exception, "Only formats of type :structure are supported"
end
result = self.try_persist(format[:content], credential)
if result.is_a?(::GxG::Database::PersistedHash)
result.set_format(format[:uuid].to_sym)
@connector[:element_hash].filter({:uuid => result.uuid.to_s}).update({:format => format[:uuid].to_s})
end
end
else
raise Exception, "Database not available"
end
else
raise Exception, "Attempted to access a defunct Database"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:search_options => (search_options)}})
end
result
end
#
def format_create(format_record=nil)
result = false
begin
if self.alive?()
if self.open?
unless self.db_permissions()[:write]
raise Exception, "Unable to write to database"
end
if format_record.is_a?(::Hash)
unless ::GxG::valid_uuid?(format_record[:uuid])
raise Exception, "Invalid UUID passed for the format"
end
unless format_record[:type].is_any?([::String, ::Symbol])
raise Exception, "You must pass the format_record[:type] as a String or Symbol"
end
unless format_record[:ufs].is_any?([::String, ::Symbol])
raise Exception, "You must pass the format_record[:ufs] as a String or Symbol"
end
unless format_record[:title].is_a?(::String)
raise Exception, "You must pass the format_record[:title] as a String"
end
unless format_record[:version].is_a?(::Numeric)
raise Exception, "You must pass the format_record[:version] as a Numeric"
end
unless format_record[:mime_types].is_any?(::String, ::Array)
raise Exception, "You must pass the format_record[:mime_types] as a String"
end
unless format_record[:content].is_a?(::Hash)
raise Exception, "You must pass the format_record[:content] as a Hash"
end
unless self.persistable?(format_record[:content])
raise Exception, "You must pass the format_record[:content] as a Hash of persistable elements"
end
if (@connector[:settings].filter({:uuid => format_record[:uuid].to_s}).count == 0 and @connector[:formats].filter({:uuid => format_record[:uuid].to_s}).count == 0 and @connector[:element_text].filter({:uuid => format_record[:uuid].to_s}).count == 0)
new_record = {:uuid => (format_record[:uuid].to_s)}
new_record[:type] = format_record[:type].to_s
new_record[:ufs] = format_record[:ufs].to_s
new_record[:title] = format_record[:title].to_s[0..256]
new_record[:version] = ::BigDecimal.new(format_record[:version].to_s)
if format_record[:mime_types].is_a?(::Array)
the_mimes = format_record[:mime_types].join(",").to_s[0..4096]
else
the_mimes = format_record[:mime_types].to_s[0..4096]
end
new_record[:mime_types] = the_mimes
content = format_record[:content].gxg_export.to_json.encode64
new_dbid = @connector[:formats].insert(new_record)
if content.size > 0
::GxG::apportioned_ranges(content.size, 4096).each_with_index do |portion, ordinal|
@connector[:text_page].insert({:parent_uuid => (new_record[:uuid].to_s), :ordinal => (ordinal), :length => (content[(portion)].size), :content => (content[(portion)])})
end
end
if new_dbid
result = true
end
#
if result == true && ::GxG::DB[:roles][:formats] == self
already = [(self)]
GxG::DB[:roles].values.each do |the_db|
unless already.include?(the_db)
the_db.format_create(format_record)
already << the_db
end
end
end
#
else
raise Exception, "That format UUID is already in use, please generate another"
end
#
else
raise ArgumentError, "You must supply a valid format record as a Hash"
end
else
raise Exception, "Database not available"
end
else
raise Exception, "Attempted to access a defunct Database"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:format_record => (format_record)}})
end
result
end
#
def format_update(format_record=nil)
format_record = format_record.clone
result = false
begin
if self.alive?()
if self.open?
unless self.db_permissions()[:write]
raise Exception, "Unable to write to database"
end
if format_record.is_a?(::Hash)
unless ::GxG::valid_uuid?(format_record[:uuid])
raise Exception, "Invalid UUID passed for the format"
end
record = @connector[:formats].filter({:uuid => format_record[:uuid].to_s}).first
if record
format_update = {}
record.keys.each do |the_key|
if [:type, :ufs, :title, :version, :mime_types].include?(the_key)
case the_key
when :type
unless format_record[:type].is_any?(::String, ::Symbol)
raise Exception, "You must pass the format_record[:type] as a String or Symbol"
end
when :ufs
unless format_record[:ufs].is_any?(::String, ::Symbol)
raise Exception, "You must pass the format_record[:ufs] as a String or Symbol"
end
when :title
unless format_record[:title].is_a?(::String)
raise Exception, "You must pass the format_record[:title] as a String"
end
when :version
unless format_record[:version].is_a?(::Numeric)
raise Exception, "You must pass the format_record[:version] as a Numeric"
end
when :mime_types
unless format_record[:mime_types].is_any?(::String, ::Array)
raise Exception, "You must pass the format_record[:mime_types] as a String"
end
end
#
if the_key == :mime_types
if format_record[:mime_types].is_a?(::Array)
the_mimes = format_record[:mime_types].join(",").to_s[0..4096]
else
the_mimes = format_record[:mime_types].to_s[0..4096]
end
format_record[:mime_types] = the_mimes
end
#
if format_record[(the_key)] != record[(the_key)]
format_update[(the_key)] = format_record[(the_key)]
end
end
end
if format_update.keys.size > 0
# FIXME: :type and :ufs are being passed as a Symbols, but DB requires them to be a strings:
if format_update[:type].is_a?(::Symbol)
format_update[:type] = format_update[:type].to_s
end
if format_update[:ufs].is_a?(::Symbol)
format_update[:ufs] = format_update[:ufs].to_s
end
@connector[:formats].filter({:uuid => format_record[:uuid].to_s}).update(format_update)
end
if format_record[:content].is_a?(::Hash)
unless self.persistable?(format_record[:content])
raise Exception, "You must pass the format_record[:content] as a Hash of persistable elements"
end
old_segments = []
content = format_record[:content].gxg_export.to_json.encode64
@connector[:text_page].select(:dbid).where({:parent_uuid => format_record[:uuid].to_s}).order(:ordinal).each do |entry|
old_segments << entry[:dbid]
end
if content.size == 0
if old_segments.size > 0
@connector[:text_page].filter({:parent_uuid => format_record[:uuid].to_s}).delete
end
else
new_segments = ::GxG::apportioned_ranges(content.size, 4096)
if old_segments.size > new_segments.size
the_count = old_segments.size
else
the_count = new_segments.size
end
manifest = []
(0..(the_count - 1)).each do |the_ordinal|
if new_segments[(the_ordinal)] and old_segments[(the_ordinal)]
#overwrite with new segment range
manifest << {:operation => :overwrite, :dbid => (old_segments[(the_ordinal)]), :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])}
else
if new_segments[(the_ordinal)]
# create new segment at this ordinal
manifest << {:operation => :create, :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])}
else
# delete old segment
manifest << {:operation => :delete, :dbid => (old_segments[(the_ordinal)])}
end
end
end
manifest.each do |action|
case action[:operation]
when :overwrite
@connector[:text_page].filter({:dbid => (action[:dbid])}).update({:ordinal => (action[:ordinal]), :length => (content[(action[:portion])].size), :content => (content[(action[:portion])])})
when :create
@connector[:text_page].insert({:parent_uuid => (record[:uuid]), :ordinal => (action[:ordinal]), :length => (content[(action[:portion])].size), :content => (content[(action[:portion])])})
when :delete
@connector[:text_page].filter({:dbid => (action[:dbid])}).delete
end
end
@connector[:formats].filter({:uuid => format_record[:uuid].to_s}).update({:version => (::BigDecimal.new(format_record[:version].to_s) + 0.0001)})
#
end
end
#
result = true
#
if result == true && ::GxG::DB[:roles][:formats] == self
already = [(self)]
GxG::DB[:roles].values.each do |the_db|
unless already.include?(the_db)
the_db.format_update(format_record)
already << the_db
end
end
end
#
else
result = self.format_create(format_record)
end
else
raise ArgumentError, "You must supply a valid format record as a Hash"
end
else
raise Exception, "Database not available"
end
else
raise Exception, "Attempted to access a defunct Database"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:format_record => (format_record)}})
end
result
end
#
def format_destroy(search_options={})
result = false
begin
if self.alive?()
if self.open?
unless self.db_permissions()[:write]
raise Exception, "Unable to write to database"
end
format_stub = self.format_list(search_options).first
if format_stub
@connector[:formats].filter({:uuid => (format_stub[:uuid].to_s)}).delete
if @connector[:text_page].filter({:parent_uuid => (format_stub[:uuid].to_s)}).count > 0
@connector[:text_page].filter({:parent_uuid => (format_stub[:uuid].to_s)}).delete
end
result = true
#
if result == true && ::GxG::DB[:roles][:formats] == self
already = [(self)]
GxG::DB[:roles].values.each do |the_db|
unless already.include?(the_db)
the_db.format_destroy(search_options)
already << the_db
end
end
end
#
end
else
raise Exception, "Database not available"
end
else
raise Exception, "Attempted to access a defunct Database"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:search_options => (search_options)}})
end
result
end
# Settings methods
def setting_keys()
result = []
begin
if self.alive?()
if self.open?()
@connector[:settings].all.each do |the_entry|
result << the_entry[:title].to_sym
end
else
raise Exception, "Database not available"
end
else
raise Exception, "Attempted to access a defunct Database"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:setting_key => setting_key}})
end
result
end
#
def [](setting_key=nil)
result = nil
begin
if self.alive?()
if self.open?()
record = @connector[:settings].filter({:title => setting_key.to_s}).first
if record
content = ""
@connector[:text_page].select(:content).where({:parent_uuid => record[:uuid].to_s}).order(:ordinal).each do |entry|
content << entry[:content]
end
if content.size > 0
result = content.unserialize()
end
end
else
raise Exception, "Database not available"
end
else
raise Exception, "Attempted to access a defunct Database"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:setting_key => setting_key}})
end
result
end
#
def []=(setting_key=nil, object=nil)
result = object
begin
if self.alive?()
if self.open?()
unless self.db_permissions()[:write]
raise Exception, "Unable to write to database"
end
unless setting_key.is_a?(::Symbol)
raise ArgumentError, "You must supply a setting key as a Symbol"
end
unless setting_key.to_s.size > 0
raise ArgumentError, "You must supply a setting key whose length is greater than zero"
end
record = @connector[:settings].filter({:title => setting_key.to_s}).first
unless record
new_uuid = ::GxG::uuid_generate().to_s
while (@connector[:settings].filter({:uuid => (new_uuid)}).count > 0 or @connector[:formats].filter({:uuid => (new_uuid)}).count > 0 or @connector[:element_text].filter({:uuid => (new_uuid)}).count > 0)
new_uuid = ::GxG::uuid_generate().to_s
end
@connector[:settings].insert({:title => setting_key.to_s, :uuid => new_uuid.to_s})
record = @connector[:settings].filter({:uuid => (new_uuid)}).first
end
if record
@connector[:settings].filter({:dbid => (record[:dbid])}).update({:version => (record[:version] + 0.0001)})
content = object.serialize()
old_segments = []
@connector[:text_page].select(:dbid).where({:parent_uuid => record[:uuid].to_s}).order(:ordinal).each do |entry|
old_segments << entry[:dbid]
end
if content.size == 0
if old_segments.size > 0
@connector[:text_page].filter({:parent_uuid => record[:uuid].to_s}).delete
end
else
new_segments = ::GxG::apportioned_ranges(content.size, 4096)
if old_segments.size > new_segments.size
the_count = old_segments.size
else
the_count = new_segments.size
end
manifest = []
(0..(the_count - 1)).each do |the_ordinal|
if new_segments[(the_ordinal)] and old_segments[(the_ordinal)]
#overwrite with new segment range
manifest << {:operation => :overwrite, :dbid => (old_segments[(the_ordinal)]), :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])}
else
if new_segments[(the_ordinal)]
# create new segment at this ordinal
manifest << {:operation => :create, :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])}
else
# delete old segment
manifest << {:operation => :delete, :dbid => (old_segments[(the_ordinal)])}
end
end
end
manifest.each do |action|
case action[:operation]
when :overwrite
@connector[:text_page].filter({:dbid => (action[:dbid])}).update({:ordinal => (action[:ordinal]), :length => (content[(action[:portion])].size), :content => (content[(action[:portion])])})
when :create
@connector[:text_page].insert({:parent_uuid => (record[:uuid]), :ordinal => (action[:ordinal]), :length => (content[(action[:portion])].size), :content => (content[(action[:portion])])})
when :delete
@connector[:text_page].filter({:dbid => (action[:dbid])}).delete
end
end
#
end
else
raise Exception, "Internal Error - could not retrieve setting record: #{setting_key.inspect}"
end
else
raise Exception, "Database not available"
end
else
raise Exception, "Attempted to access a defunct Database"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:setting => setting_key, :object => object}})
end
result
end
#
# Permission policy support:
def permission_policy()
result = nil
begin
if self.alive?()
if self.open?()
if @authority
result = @authority.permission_policy()
else
result = @permission_policy
end
else
raise Exception, "Database not available"
end
else
raise Exception, "Attempted to access a defunct Database"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {}})
end
result
end
#
def permission_policy_load()
result = false
begin
if self.alive?()
if self.open?()
if @authority
result = @authority.permission_policy_load()
else
@permission_policy = (self[:permission_policy] || [])
result = true
end
else
raise Exception, "Database not available"
end
else
raise Exception, "Attempted to access a defunct Database"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {}})
end
result
end
#
def permission_policy_save()
result = false
begin
if self.alive?()
if self.open?()
if @authority
result = @authority.permission_policy_save()
else
self[:permission_policy] = (@permission_policy || [])
result = true
end
else
raise Exception, "Database not available"
end
else
raise Exception, "Attempted to access a defunct Database"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {}})
end
result
end
#
def enforce_permission_policy(event={})
# Event format: enforce_permission_policy({:action => :extend, :credential => nil, :source => nil, :destination => nil})
# Rule format: {:event => :create, :credential => :any, :action => {:extend_permissions => nil}}
the_policy = self.permission_policy()
result = false
begin
if self.alive?()
if self.open?()
actions = []
the_policy.each do |rule|
if event[:action] == rule[:event]
if rule[:credential] == event[:credential] || rule[:credential] == :any
actions << rule[:action]
end
end
end
actions.each do |action|
case action.keys[0]
when :extend_permissions
if action[:use_permission].is_a?(::Hash)
use_permission = action[:use_permission]
else
use_permission = nil
end
credential = event[:credential]
unless ::GxG::valid_uuid?(credential)
raise ArgumentError, "You MUST provide a valid UUID as :credential."
end
new_credential = action[:extend_permissions]
source = event[:source]
unless source.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
raise ArgumentError, "You MUST provide a persisted object as :source."
end
unless source.db_address
raise Exception, "You provided a Deactivated object as :source."
end
destination = event[:destination]
unless destination.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
raise ArgumentError, "You MUST provide a persisted object as :source."
end
unless destination.db_address
raise Exception, "You provided a Deactivated object as :destination."
end
#
permissions = self.element_permissions(source.db_address()[:table],source.db_address()[:dbid])
permission_db = []
if [:persisted_array, :persisted_hash].include?(destination.db_address()[:table])
search_db = [{:table => destination.db_address()[:table], :dbid => destination.db_address()[:dbid]}]
permission_db << {:table => destination.db_address()[:table], :dbid => destination.db_address()[:dbid]}
while search_db.size > 0 do
entry = search_db.shift
self.element_manifest(entry[:table],entry[:dbid],false).each do |item|
if [:persisted_array, :persisted_hash].include?(item[:table])
search_db << item
permission_db << item
end
end
end
permission_db.each do |the_reference|
permissions.each do |record|
self.assign_element_permission(the_reference[:table],the_reference[:dbid], credential, record[:permissions])
if use_permission
self.assign_element_permission(the_reference[:table],the_reference[:dbid], new_credential, use_permission)
else
self.assign_element_permission(the_reference[:table],the_reference[:dbid], new_credential, record[:permissions])
end
end
end
# destination.search do |the_value, the_selector, the_container|
# if [:persisted_array, :persisted_hash].include?(the_value.db_address()[:table])
# permission_db << {:table => the_value.db_address()[:table], :dbid => the_value.db_address()[:dbid]}
# end
# end
#
end
# permission_db << {:table => destination.db_address()[:table], :dbid => destination.db_address()[:dbid]}
# if destination.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
# destination.search do |the_value, the_selector, the_container|
# permission_db << {:table => the_value.db_address()[:table], :dbid => the_value.db_address()[:dbid]}
# end
# end
# permission_db.each do |item|
# self.element_manifest(item[:table],item[:dbid],false).each do |entry|
# permissions.each do |record|
# self.assign_element_permission(entry[:table],entry[:dbid], credential, record[:permissions])
# if use_permission
# self.assign_element_permission(entry[:table],entry[:dbid], new_credential, use_permission)
# else
# self.assign_element_permission(entry[:table],entry[:dbid], new_credential, record[:permissions])
# end
# end
# end
# end
#
end
end
result = true
else
raise Exception, "Database not available"
end
else
raise Exception, "Attempted to access a defunct Database"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:event => event}})
end
result
end
# ### Import methods (deprecated in favor of sychronize_records)
def import(credential=nil, the_record=nil)
#
result = nil
if self.alive?
if self.open?
if self.db_permissions()[:write]
cleanup_manifest = []
begin
unless ::GxG::valid_uuid?(credential)
raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)"
end
if the_record.is_a?(::Hash)
case the_record[:type]
when :element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime
new_dbid = @connector[(the_record[:type])].insert({:version => (the_record[:version]), :content => (the_record[:content])})
self.assign_element_permission((the_record[:type]), (new_dbid), credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
result = {:table => (the_record[:table]), :dbid => (new_dbid)}
when :element_text
the_uuid = ::GxG::uuid_generate()
while (@connector[:element_text].filter({:uuid => the_uuid}).count > 0)
the_uuid = ::GxG::uuid_generate()
end
new_dbid = @connector[:element_text].insert({:uuid => (the_uuid), :version => (the_record[:version]), :length => (the_record[:content].size)})
cleanup_manifest << {:table => :element_text, :dbid => (new_dbid)}
::GxG::apportioned_ranges(the_record[:content].size, 4096).each_with_index do |the_range,the_ordinal|
@connector[:text_page].insert({:parent_uuid => (the_uuid), :ordinal => (the_ordinal), :length => (the_record[:content][(the_range)].size), :content => (the_record[:content][(the_range)])})
end
self.assign_element_permission((the_record[:type]), (new_dbid), credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
result = {:table => :element_text, :dbid => (new_dbid)}
when :element_binary
the_uuid = ::GxG::uuid_generate()
while (@connector[:element_binary].filter({:uuid => the_uuid}).count > 0)
the_uuid = ::GxG::uuid_generate()
end
new_dbid = @connector[:element_binary].insert({:uuid => (the_uuid), :version => (the_record[:version]), :length => (the_record[:content].size)})
cleanup_manifest << {:table => :element_binary, :dbid => (new_dbid)}
::GxG::apportioned_ranges(the_record[:content].size, 65536).each_with_index do |the_range,the_ordinal|
@connector[:binary_page].insert({:parent_uuid => (the_uuid), :ordinal => (the_ordinal), :length => (the_record[:content].data()[(the_range)].size), :content => (the_record[:content].data()[(the_range)])})
end
self.assign_element_permission((the_record[:type]), (new_dbid), credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
result = {:table => :element_binary, :dbid => (new_dbid)}
when :element_array
# {:type => :element_array, :uuid => @uuid, :title => @title, :version => @version, :content => []}
existing = @connector[(the_record[:type])].select(:dbid, :version).where({:uuid => the_record[:uuid].to_s}).first
if existing
if the_record[:version] > existing[:version]
unless self.element_destroy(credential, :element_array, existing[:dbid])
raise Exception, "Error replacing array element - Aborting"
end
existing = nil
else
log_warn("Import record is older than existing one - Skipping")
new_dbid = existing[:dbid]
end
end
unless existing
new_dbid = @connector[(the_record[:type])].insert({:uuid => (the_record[:uuid].to_s), :title => (the_record[:title]), :version => (the_record[:version])})
cleanup_manifest << {:table => :element_array, :dbid => (new_dbid)}
self.assign_element_permission(:element_array, (new_dbid), credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
the_record[:content].each_with_index do |element, indexer|
new_address = self.import(credential, (element))
if new_address
cleanup_manifest << new_address
@connector[:array_links].insert({:parent_uuid => (the_record[:uuid].to_s), :ordinal => (indexer), :element => (::GxG::Database::Database::element_table_index(new_address[:table])), :elementid => new_address[:dbid]})
else
raise Exception, "Error processing the import record - Aborting"
end
end
end
result = {:table => :element_array, :dbid => (new_dbid)}
when :element_hash
# {:type => :element_hash, :uuid => @uuid, :title => @title, :version => @version, :format => @format, :content => {}}
existing = @connector[(the_record[:type])].select(:dbid, :version).where({:uuid => the_record[:uuid].to_s}).first
if existing
if the_record[:version] > existing[:version]
unless self.element_destroy(credential, :element_hash, existing[:dbid])
raise Exception, "Error replacing hash element - Aborting"
end
existing = nil
else
log_warn("Import record is older than existing one - Skipping")
new_dbid = existing[:dbid]
end
end
unless existing
new_dbid = @connector[(the_record[:type])].insert({:uuid => (the_record[:uuid].to_s), :title => (the_record[:title]), :version => (the_record[:version]), :format => (the_record[:format].to_s)})
cleanup_manifest << {:table => :element_hash, :dbid => (new_dbid)}
self.assign_element_permission(:element_hash, (new_dbid), credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
the_record[:content].keys.each_with_index do |the_key, indexer|
new_address = self.import(credential, (the_record[:content][(the_key)]))
if new_address
cleanup_manifest << new_address
@connector[:hash_links].insert({:parent_uuid => (the_record[:uuid].to_s), :property => (the_key.to_s), :ordinal => (indexer), :element => (::GxG::Database::Database::element_table_index(new_address[:table])), :elementid => new_address[:dbid]})
else
raise Exception, "Error processing the import record - Aborting"
end
end
end
result = {:table => :element_hash, :dbid => (new_dbid)}
else
raise ArgumentError, "Invalid table specified"
end
else
raise ArgumentError, "You must supply an import record as a Hash"
end
rescue Exception => the_error
# clean up
cleanup_manifest.each do |entry|
self.element_destroy(credential, entry[:table], entry[:dbid])
end
log_error({:error => the_error, :parameters => {:the_record => (the_record)}})
end
else
log_warn("Cannot import records into a read-only Database - Aborting")
end
end
end
result
end
# ### Search function method
def uuid_list(credential=nil, criteria={})
# will return an array of all structures the credential has (at least) read access to
result = []
begin
if self.alive?
if self.open?
unless ::GxG::valid_uuid?(credential)
raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)"
end
unless criteria.is_a?(::Hash)
raise ArgumentError, "You must supply a search criteria as a Hash"
end
hash_criteria = {}
array_criteria = {}
# Limit, Offset, and Page selection
if criteria[:limit].is_a?(::Integer)
limit = criteria[:limit]
else
limit = nil
end
if criteria[:offset].is_a?(::Integer)
offset = criteria[:offset]
else
offset = 0
end
if criteria[:page].is_a?(::Integer)
page = criteria[:page]
unless limit
limit = 100
end
offset = ((page * limit) - 1)
else
page = nil
end
# UUID, Title, and Format searches
if ::GxG::valid_uuid?(criteria[:uuid])
hash_criteria[:uuid] = criteria[:uuid].to_s
array_criteria[:uuid] = criteria[:uuid].to_s
end
if criteria[:title].is_a?(::String)
hash_criteria[:title] = criteria[:title].to_s[0..256]
array_criteria[:title] = criteria[:title].to_s[0..256]
end
if criteria[:format].is_any?([::String, ::Symbol])
if ::GxG::valid_uuid?(criteria[:format])
format_list = self.format_list({:uuid => criteria[:format].to_sym})
unless format_list.size > 0
raise ArgumentError, "Format not found: #{criteria[:format].inspect}"
end
hash_criteria[:format] = criteria[:format].to_s
end
end
if criteria[:ufs].is_any?(::String, ::Symbol)
if criteria[:ufs_version].is_a?(::Numeric)
format_list = self.format_list({:ufs => criteria[:ufs].to_sym, :version => ::BigDecimal.new(criteria[:ufs_version].to_s)})
else
format_list = self.format_list({:ufs => criteria[:ufs].to_sym})
end
unless format_list.size > 0
if criteria[:ufs_version].is_a?(::Float)
raise ArgumentError, "Format not found: #{criteria[:ufs].inspect}, Version: #{criteria[:ufs_version].inspect}"
else
raise ArgumentError, "Format not found: #{criteria[:ufs].inspect}"
end
end
hash_criteria[:format] = format_list[0][:uuid].to_s
end
# Output column selection: (very very basic at first)
selectables = [:uuid, :title, :version]
selected = []
if criteria[:select].is_any?(::Symbol, ::Array)
if criteria[:select].is_a?(::Array)
criteria[:select].each do |the_column_symbol|
if selectables.include?(the_column_symbol)
selected << the_column_symbol
end
end
else
if selectables.include?(criteria[:select])
selected << criteria[:select]
end
end
end
if selected.size == 0
selected << :uuid
end
#
reduce_to_datetime = Proc.new do |the_value|
the_result = nil
db_content = nil
if the_value.is_a?(::String)
if the_value.include?("between")
the_times = the_value.split("between")[1].split("and")
the_times[0] = ::Chronic.parse(the_times[0])
the_times[1] = ::Chronic.parse(the_times[1])
if the_times[0].is_a?(::Time) && the_times[1].is_a?(::Time)
db_content = ((the_times[0].to_datetime)..(the_times[1].to_datetime))
else
raise Exception, "Unable to understand your time specification text: #{the_value.inspect}"
end
else
if the_value.valid_time? || the_value.valid_date? || the_value.valid_datetime_nolocale? || the_value.valid_datetime?
db_content = ::DateTime::parse(the_value)
else
the_times = ::Chronic.parse(the_value)
if the_times.is_a?(::Time)
db_content = the_times.to_datetime
else
raise Exception, "Unable to understand your time specification text: #{the_value.inspect}"
end
end
end
end
if the_value.is_a?(::Range)
the_times = [(the_value.first), (the_value.last)]
if the_times[0].is_a?(::Time)
the_times[0] = the_times[0].to_datetime
end
if the_times[0].is_a?(::Date)
the_times[0] = ::DateTime::parse(the_times[0].to_s)
end
if the_times[1].is_a?(::Time)
the_times[1] = the_times[1].to_datetime
end
if the_times[1].is_a?(::Date)
the_times[1] = ::DateTime::parse(the_times[1].to_s)
end
the_times = ((the_times[0])..(the_times[1]))
if the_times.first.is_a?(::DateTime) && the_value.last.is_a?(::DateTime)
db_content = the_times
else
raise Exception, "Invalid Range of dates, use DateTime objects in the Range: #{the_value.inspect}"
end
end
if the_value.is_any?(::Time, ::Date)
db_content = ::DateTime::parse(the_value.to_s)
end
if the_value.is_a?(::DateTime)
db_content = the_value
end
if db_content
result = db_content
else
raise Exception, "Unable to reduce to a DateTime with this input: #{the_value.inspect}"
end
the_result
end
#
db_content_value = Proc.new do |the_value|
the_result = nil
the_table = ::GxG::Database::Database::element_table_for_instance(the_value)
if the_table == :unspecified
raise Exception, "Invalid value passed as persistable: #{the_value.inspect}"
else
db_content = nil
case the_table
when :element_boolean
if the_value.is_a?(::TrueClass)
db_content = 1
else
if the_value.is_a?(::FalseClass)
db_content = 0
else
db_content = -1
end
end
when :element_binary
db_content = ""
the_value.each do |the_byte|
db_content << the_byte.to_s(base=16)
end
when :element_text
if the_value.valid_time? || the_value.valid_date? || the_value.valid_datetime_nolocale? || the_value.valid_datetime?
db_content = reduce_to_datetime.call(the_value)
else
db_content = the_value
end
else
db_content = the_value
end
if db_content
the_result = db_content
else
raise Exception, "Could not cast the value to something useful: #{the_value.inspect}"
end
end
the_result
end
#
find_property_records = Proc.new do |a_selector, a_container|
a_result = []
a_container.each do |item|
if item[(a_selector)]
a_result << item
end
end
a_result
end
find_element_records = Proc.new do |a_selector, a_container|
a_result = []
a_container.each do |item|
if item[(a_selector)]
a_result << item
end
end
a_result
end
found_list = []
# criteria[:property] :
# For all
# {:property_name => {:equals => value}}
# For :element_integer, :element_float, :element_bigdecimal
# {:property_name => {:greater_than => value, :greater_or_equal => value, :less_than => value, :less_or_equal => value}}
# For :element_datetime, Note: if you pass text as a date ::Chronic will be used to parse it.
# {:property_name => {:at => value, :before => value, :at_or_before => value, :after => value, :at_or_after => value, :between => <range-value>}
# For Text, Binary
# {:property_name => {:include => value}}
#
# criteria[:element] :
# similar to criteria[:property] except that the entire table for the type is searched and no property dereferencing needed.
#
if criteria[:property] || criteria[:element]
# Expansion
# Text Search: @connector[:text_page].where(::Sequel.like(:content,'%#{search_text}%'))
# Binary Search: @connector[:binary_page].where(::Sequel.like(:content,'%#{byte_sequence}%'))
# self.element_parent_address(the_table=:unspecified, the_dbid=0)
if criteria[:property].is_a?(::Hash)
# Only ONE property key can be compared (TODO: expand to multiple keys)
property_matches = []
last_key = nil
#
criteria[:property].keys.each do |property_key|
# compare each key's value to qualify the parent object:
# {:html => {:include => "\n"}}
comparison_op = criteria[:property][(property_key)].keys[0].to_sym
if criteria[:property][(property_key)][(comparison_op)].is_any?(::String, ::Range) && [:at, :before, :after, :at_or_before, :at_or_after, :between].include?(comparison_op)
comparison_value = reduce_to_datetime.call(criteria[:property][(property_key)][(comparison_op)])
table_test = ::GxG::Database::Database::element_table_for_instance(comparison_value)
else
table_test = ::GxG::Database::Database::element_table_for_instance(criteria[:property][(property_key)][(comparison_op)])
comparison_value = db_content_value.call(criteria[:property][(property_key)][(comparison_op)])
end
#
if [:element_hash, :element_array].include?(table_test)
# Only properties with base elements can be compared - not structures (TODO: expand this)
log_warn("Attempted to compare a structure as a property value (not supported, ignoring).")
next
end
# Review : rewrite - adapt to new arch.
match_uuid_list = []
# comparison_value
@connector[:hash_properties].filter({:property => property_key.to_s}).each do |entry|
element_value = nil
case entry[:element]
when "element_boolean"
case entry[:element_boolean]
when -1
element_value = nil
when 0
element_value = false
when 1
element_value = nil
end
when "element_integer"
element_value = entry[:element_integer]
when "element_float"
element_value = entry[:element_float]
when "element_bigdecimal"
element_value = ::BigDecimal.new(entry[:element_bigdecimal].to_s)
when "element_datetime"
element_value = entry[:element_datetime]
when "element_text"
if entry[:element_text_uuid].to_s.size > 0
# Review: issue with Base64: decode64 mangles non-base64 text but does it w/o error now.
element_value = ""
@connector[:text_page].filter({:uuid => entry[:element_text_uuid].to_s}).order(:ordinal).each do |text_page|
element_value << text_page[:content]
end
else
element_value = entry[:element_text]
end
when "element_binary"
element_value = ::GxG::ByteArray.new
@connector[:binary_page].filter({:uuid => entry[:element_binary_uuid].to_s}).order(:ordinal).each do |binary_page|
element_value << binary_page[:content]
end
end
#
begin
case comparison_op
when :equals, :at
if element_value == comparison_value
match_uuid_list << entry[:parent_uuid].to_sym
end
when :greater_than, :after
if element_value > comparison_value
match_uuid_list << entry[:parent_uuid].to_sym
end
when :greater_or_equal, :at_or_after
if element_value >= comparison_value
match_uuid_list << entry[:parent_uuid].to_sym
end
when :less_than, :before
if element_value < comparison_value
match_uuid_list << entry[:parent_uuid].to_sym
end
when :less_or_equal, :at_or_before
if element_value <= comparison_value
match_uuid_list << entry[:parent_uuid].to_sym
end
when :between
if comparison_value.include?(element_value)
match_uuid_list << entry[:parent_uuid].to_sym
end
when :include
if element_value.to_s.include?(comparison_value.to_s)
match_uuid_list << entry[:parent_uuid].to_sym
end
when :match
if element_value.to_s.match(comparison_value)
match_uuid_list << entry[:parent_uuid].to_sym
end
end
rescue Exception => cast_error
# Probably a type casting error (comparison of dissimilar types)
next
end
end
#
match_uuid_list.each do |the_matched_uuid|
# Add to main found list
parent_record = @connector[:element_hash].select(:uuid, :version, :title).where({:uuid => the_matched_uuid.to_s}).first
if parent_record
new_record = {}
selected.each do |the_selected_key|
if parent_record.keys.include?(the_selected_key)
new_record[(the_selected_key)] = parent_record[(the_selected_key)]
if ::GxG::valid_uuid?(new_record[(the_selected_key)])
new_record[(the_selected_key)] = new_record[(the_selected_key)].to_sym
end
end
end
unless found_list.include?(new_record)
found_list << new_record
end
end
end
#
if last_key != property_key
last_key = property_key
end
#
end
#
else
if criteria[:element].is_a?(::Array)
criteria[:element].each_with_index do |the_element, element_index|
comparison_op = criteria[:element][(element_index)].keys[0].to_sym
if criteria[:element][(element_index)][(comparison_op)].is_any?(::String, ::Range) && [:at, :before, :after, :at_or_before, :at_or_after, :between].include?(comparison_op)
comparison_value = reduce_to_datetime.call(criteria[:element][(element_index)][(comparison_op)])
element_table = ::GxG::Database::Database::element_table_for_instance(comparison_value)
else
element_table = ::GxG::Database::Database::element_table_for_instance(criteria[:element][(element_index)][(comparison_op)])
comparison_value = db_content_value.call(criteria[:element][(element_index)][(comparison_op)])
end
# Structural comparison not supported.
if [:element_hash, :element_array].include?(element_table)
log_warn("Attempted to compare a structure as an element (not supported, ignoring).")
next
end
match_uuid_list = []
# comparison_value
@connector[:array_elements].filter({:element => element_table.to_s}).each do |entry|
element_value = nil
case entry[:element]
when "element_boolean"
case entry[:element_boolean]
when -1
element_value = nil
when 0
element_value = false
when 1
element_value = nil
end
when "element_integer"
element_value = entry[:element_integer]
when "element_float"
element_value = entry[:element_float]
when "element_bigdecimal"
element_value = ::BigDecimal.new(entry[:element_bigdecimal].to_s)
when "element_datetime"
element_value = entry[:element_datetime]
when "element_text"
if entry[:element_text_uuid].to_s.size > 0
# Review: issue with Base64: decode64 mangles non-base64 text but does it w/o error now.
element_value = ""
@connector[:text_page].filter({:uuid => entry[:element_text_uuid].to_s}).order(:ordinal).each do |text_page|
element_value << text_page[:content]
end
else
element_value = entry[:element_text]
end
when "element_binary"
element_value = ::GxG::ByteArray.new
@connector[:binary_page].filter({:uuid => entry[:element_binary_uuid].to_s}).order(:ordinal).each do |binary_page|
element_value << binary_page[:content]
end
end
#
begin
case comparison_op
when :equals, :at
if element_value == comparison_value
match_uuid_list << entry[:parent_uuid].to_sym
end
when :greater_than, :after
if element_value > comparison_value
match_uuid_list << entry[:parent_uuid].to_sym
end
when :greater_or_equal, :at_or_after
if element_value >= comparison_value
match_uuid_list << entry[:parent_uuid].to_sym
end
when :less_than, :before
if element_value < comparison_value
match_uuid_list << entry[:parent_uuid].to_sym
end
when :less_or_equal, :at_or_before
if element_value <= comparison_value
match_uuid_list << entry[:parent_uuid].to_sym
end
when :between
if comparison_value.include?(element_value)
match_uuid_list << entry[:parent_uuid].to_sym
end
when :include
if element_value.to_s.include?(comparison_value.to_s)
match_uuid_list << entry[:parent_uuid].to_sym
end
when :match
if element_value.to_s.match(comparison_value)
match_uuid_list << entry[:parent_uuid].to_sym
end
end
rescue Exception => cast_error
# Probably a type casting error (comparison of dissimilar types)
next
end
# end - comparison loop
end
#
match_uuid_list.each do |the_matched_uuid|
# Add to main found list
parent_record = @connector[:element_array].select(:uuid, :version, :title).where({:uuid => the_matched_uuid.to_s}).first
if parent_record
new_record = {}
selected.each do |the_selected_key|
if parent_record.keys.include?(the_selected_key)
new_record[(the_selected_key)] = parent_record[(the_selected_key)]
if ::GxG::valid_uuid?(new_record[(the_selected_key)])
new_record[(the_selected_key)] = new_record[(the_selected_key)].to_sym
end
end
end
unless found_list.include?(new_record)
found_list << new_record
end
end
end
#
# unless last_index == element_index
# last_index = element_index
# end
end
#
else
# Criteria Expansion Here
end
end
else
@connector[:element_hash].filter(hash_criteria).each do |entry|
new_record = {}
selected.each do |the_key|
if ::GxG::valid_uuid?(entry[(the_key)])
new_record[(the_key)] = entry[(the_key)].to_sym
else
new_record[(the_key)] = entry[(the_key)]
end
end
found_list << new_record
end
#
unless hash_criteria[:format]
@connector[:element_array].filter(array_criteria).each do |entry|
new_record = {}
selected.each do |the_key|
if ::GxG::valid_uuid?(entry[(the_key)])
new_record[(the_key)] = entry[(the_key)].to_sym
else
new_record[(the_key)] = entry[(the_key)]
end
end
found_list << new_record
end
end
end
#
# Sorting Options Here: (Expansion)
if criteria[:order].is_any?(::Symbol, ::Hash)
ascending = true
sort_key = nil
if criteria[:order].is_a?(::Hash)
if criteria[:order][:ascending].is_a?(::Symbol)
sort_key = criteria[:order][:ascending]
end
if criteria[:order][:descending].is_a?(::Symbol)
sort_key = criteria[:order][:descending]
ascending = false
end
else
sort_key = criteria[:order]
end
if selected.include?(sort_key)
found_list = found_list.sort do |entry, another|
entry[(sort_key)] <=> another[(sort_key)]
end
unless ascending
found_list.reverse!
end
end
end
# Impose result set limit and offset
if limit.is_a?(::Integer)
if found_list.size > 0
if (offset + (limit - 1)) <= (found_list.size - 1)
found_list = found_list[((offset)..(offset + (limit - 1)))]
else
if offset <= (found_list.size - 1)
found_list = found_list[((offset)..(-1))]
end
end
end
end
#
found_list.size.times do
result << found_list.shift
end
#
else
raise Exception, "Database not available"
end
else
raise Exception, "Attempted to access a defunct Database"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:credential => (credential), :criteria => (criteria)}})
end
result
end
#
def search_database(credential=nil, criteria={})
# Usage: search_database(credential, {:ufs => "the.ufs.code", :properties => [{:the_property => {:equals => value}}]})
#
# criteria[:properties] :
# For all
# {:property_name => {:equals => value}}
# For :element_integer, :element_float, :element_bigdecimal
# {:property_name => {:greater_than => value, :greater_or_equal => value, :less_than => value, :less_or_equal => value}}
# For :element_datetime, Note: if you pass text as a date ::Chronic will be used to parse it.
# {:property_name => {:at => value, :before => value, :at_or_before => value, :after => value, :at_or_after => value, :between => <range-value>}
# For Text, Binary
# {:property_name => {:include => value}}
#
# criteria[:element] :
# similar to criteria[:property] except that the entire table for the type is searched and no property dereferencing needed.
#
result = []
criterium = []
criteria_error = false
unless criteria.is_a?(::Hash)
criteria_error = true
end
unless criteria_error
common_criteria = criteria.clone
properties = (common_criteria.delete(:properties) || [])
unless properties.is_a?(::Array)
criteria_error = true
end
elements = (common_criteria.delete(:elements) || [])
unless elements.is_a?(::Array)
criteria_error = true
end
end
#
unless criteria_error
if properties.size > 0 && elements.size > 0
criteria_error = true
log_warn("You MUST select EITHER :properites OR :elements, not both.")
end
end
#
unless criteria_error
# Prepare criterium
if common_criteria[:select].is_a?(::Array)
unless common_criteria[:select].include?(:uuid)
common_criteria[:select] << :uuid
end
else
common_criteria[:select] = [:uuid]
end
#
if common_criteria[:ufs]
criterium << {:ufs => common_criteria[:ufs]}
end
if common_criteria[:format]
criterium << {:format => common_criteria[:format]}
end
#
if properties.size > 0
properties.each do |the_property_search|
criterium << ((common_criteria.clone).merge({:property => the_property_search}))
end
end
if elements.size > 0
elements.each do |the_element_search|
criterium << ((common_criteria.clone).merge({:element => the_element_search}))
end
end
end
#
result_sets = []
# if properties.size == 0 && elements.size == 0
# result_sets << self.uuid_list(credential, criteria)
# else
criterium.each do |the_criteria|
result_sets << self.uuid_list(credential, the_criteria)
end
# end
scores = {}
result_sets.each do |the_result|
the_result.each do |the_answer|
if scores.keys.include?(the_answer[:uuid])
scores[(the_answer[:uuid])] = (scores[(the_answer[:uuid])] + 1)
else
scores[(the_answer[:uuid])] = 1
end
end
end
scores.keys.each do |the_uuid|
if scores[(the_uuid)] == result_sets.size
result_sets[0].each do |the_record|
if the_record[:uuid] == the_uuid
result << the_record
break
end
end
end
end
#
result
end
# Synchronize records: (and supports)
#
def synchronize_records(the_records=[], credential=nil, options={})
# Review : rewrite for new architecture.
result = {:status => :completed}
#
total_records_processed = 0
total_formats_processed = 0
# List of format uuids needed from the OTHER database to complete the sync.
formats_needed = []
# List of format records we are sharing.
formats_provided = []
# records on THIS database that are higher version than submitted version.
updates_provided = []
# In case something goes horribly wrong:
clean_up_list = []
#
begin
if self.alive?
if self.open?
unless ::GxG::valid_uuid?(credential)
raise ArgumentError, "You MUST provide a valid UUID as credential."
end
#
if the_records.is_a?(::String)
if the_records.base64?
the_records = the_records.decode64
end
if the_records.json?
the_records = ::JSON.parse(the_records,{:symbolize_names => true})
end
end
if the_records.is_a?(::Hash)
the_records = [(the_records)]
end
unless the_records.is_a?(::Array)
raise ArgumentError, "You MUST provide: Base64, JSON, Hash, or Array of operation Hashes."
end
# Process each operation in the_records, data is inline under :data key.
the_records.each do |the_operation|
if the_operation.is_a?(::Hash)
if [:merge, :delete, :fetch, :merge_format, :delete_format, :formats_provided, :formats_needed].include?(the_operation[:operation])
#
case the_operation[:operation]
when :merge
unless self.db_permissions()[:write]
raise Exception, "Attempted to alter a read-only database."
end
manifest = []
if the_operation[:data].is_a?(::Array)
manifest = the_operation[:data]
else
if the_operation[:data].is_a?(::Hash)
manifest = [(the_operation[:data])]
else
log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}")
end
end
manifest.each do |entry|
if entry[:format]
unless self.format_list({:uuid => (entry[:format])}).size > 0
raise Exception, "Attempted to merge with unknown :format #{entry[:format].inspect}. Synchronize formats first, then records."
end
end
# fetch existing:
# if existing - compare versions - if greater provide, if lesser update, else add to db.
queue = [(entry)]
while queue.size > 0 do
record = queue.shift
if ::GxG::valid_uuid?(record[:uuid])
formatting = {:format => nil, :constraint => nil}
existing = self.retrieve_by_uuid(record[:uuid], credential)
unless existing
# 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[(record[:type].to_s.downcase.to_sym)].filter({:uuid => record[:uuid].to_s}).count > 0
# This prevents no-element-retrieved-due-to-permission issue causing an 'add duplicate uuid object' situation:
dbid = @connector[(record[:type].to_s.downcase.to_sym)].insert({:uuid => record[:uuid].to_s, :title => "Merged Persisted Object"})
self.assign_element_permission(record[:type].to_s.downcase.to_sym,dbid,credential,{:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
#
end
existing = self.retrieve_by_uuid(record[:uuid], credential)
end
if existing
# ### Compare versions
if existing.version > record[:version]
action = :provide
else
if existing.version < record[:version]
action = :update
else
action = :ignore
end
end
# ### Take Action
case action
when :provide
updates_provided << {:operation => :merge, :data => [(existing.export())]}
existing.deactivate
total_records_processed += 1
when :update
# ### Prelude
if record[:format].to_s.size > 0
formatting[:format] = record[:format].to_s
end
if record[:constraint].to_s.size > 0
formatting[:constraint] = record[:constraint].to_s
end
clean_up_list << existing
existing.get_reservation()
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)
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
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)
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
end
end
end
else
if record[:content].is_a?(::Array)
# Eliminate elements that the imported record does not have. (tracking structural changes)
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
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
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
end
#
end
when :delete
unless self.db_permissions()[:write]
raise Exception, "Attempted to alter a read-only database."
end
#
manifest = []
if the_operation[:data].is_a?(::Array)
manifest = the_operation[:data]
else
if the_operation[:data].is_a?(::Hash)
manifest = [(the_operation[:data])]
else
log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}")
end
end
manifest.each do |entry|
if ::GxG::valid_uuid?(entry[:uuid])
existing = self.retrieve_by_uuid(entry[:uuid].to_sym, credential)
if existing
the_address = existing.db_address()
existing.deactivate
if self.element_destroy(credential,the_address[:table],the_address[:dbid])
total_records_processed += 1
else
log_warn("Record #{entry[:uuid].inspect} was not deleted.")
end
else
log_warn("Record #{entry[:uuid].inspect} was not found.")
end
else
log_warn("Invalid UUID specifier (ignoring): #{entry[:uuid].inspect}")
end
end
#
when :fetch
# wrap export record in a :merge operation and add to updates_provided.
manifest = []
if the_operation[:data].is_a?(::Array)
manifest = the_operation[:data]
else
if the_operation[:data].is_a?(::Hash)
manifest = [(the_operation[:data])]
else
log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}")
end
end
manifest.each do |entry|
if ::GxG::valid_uuid?(entry[:uuid])
existing = self.retrieve_by_uuid(entry[:uuid].to_sym, credential)
if existing
updates_provided << {:operation => :merge, :data => [(existing.export())]}
existing.deactivate
total_records_processed += 1
else
log_warn("Record #{entry[:uuid].inspect} was not found.")
end
else
log_warn("Invalid UUID specifier (ignoring): #{entry[:uuid].inspect}")
end
end
#
when :merge_format
unless self.db_permissions()[:write]
raise Exception, "Attempted to alter a read-only database."
end
# add format record if missing - check for existing, and provide if ours is newer.
manifest = []
if the_operation[:data].is_a?(::Array)
manifest = the_operation[:data]
else
if the_operation[:data].is_a?(::Hash)
manifest = [(the_operation[:data])]
else
log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}")
end
end
manifest.each do |entry|
if ::GxG::valid_uuid?(entry[:uuid])
existing = self.format_load({:uuid => entry[:uuid].to_sym})
if existing.is_a?(::Hash)
if existing[:version] < entry[:version]
if self.format_update(entry)
total_formats_processed += 1
else
log_warn("Could not update Format #{entry[:uuid].inspect}")
end
end
if existing[:version] > entry[:version]
unless formats_provided.include?(existing)
formats_provided << existing
total_formats_processed += 1
end
end
else
if self.format_create(entry)
total_formats_processed += 1
else
log_warn("Could not create Format #{entry[:uuid].inspect}")
end
end
else
log_warn("Invalid Format specifier (ignoring): #{entry[:uuid].inspect}")
end
end
when :delete_format
unless self.db_permissions()[:write]
raise Exception, "Attempted to alter a read-only database."
end
# If the format is in use, log_warn that you're ignoring the directive.
manifest = []
if the_operation[:data].is_a?(::Array)
manifest = the_operation[:data]
else
if the_operation[:data].is_a?(::Hash)
manifest = [(the_operation[:data])]
else
log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}")
end
end
manifest.each do |entry|
if ::GxG::valid_uuid?(entry[:uuid])
if @connector[:element_hash].filter({:format => entry[:uuid].to_s}).count == 0 && @connector[:element_array].filter({:constraint => entry[:uuid].to_s}).count == 0
# Not in use, safe to delete
if self.format_destroy({:uuid => entry[:uuid].to_sym})
total_formats_processed += 1
end
else
log_warn("Format #{entry[:uuid].inspect} in use (ignorning).")
end
else
log_warn("Invalid format specifier on :delete directive (ignorning): #{entry[:uuid].inspect}")
end
end
when :formats_provided
unless self.db_permissions()[:write]
raise Exception, "Attempted to alter a read-only database."
end
# compare uuids against db-installed formats and add to formats_needed if missing.
manifest = []
if the_operation[:data].is_a?(::Array)
manifest = the_operation[:data]
else
if the_operation[:data].is_a?(::Hash)
manifest = [(the_operation[:data])]
else
log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}")
end
end
manifest.each do |entry|
if ::GxG::valid_uuid?(entry[:uuid])
if self.format_list({:uuid => (entry[:uuid])}).size == 0
record = {:uuid => (entry[:uuid].to_sym)}
unless formats_needed.include?(record)
formats_needed << record
total_formats_processed += 1
end
end
else
log_warn("Invalid Format specifier (ignoring): #{entry[:uuid].inspect}")
end
end
when :formats_needed
# Gather up existing format records to share.
manifest = []
if the_operation[:data].is_a?(::Array)
manifest = the_operation[:data]
else
if the_operation[:data].is_a?(::Hash)
manifest = [(the_operation[:data])]
else
log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}")
end
end
manifest.each do |entry|
if ::GxG::valid_uuid?(entry[:uuid])
existing = self.format_load({:uuid => entry[:uuid].to_sym})
if existing.is_a?(::Hash)
unless formats_provided.include?(existing)
formats_provided << existing
total_formats_processed += 1
end
end
else
log_warn("Invalid Format specifier (ignoring): #{entry[:uuid].inspect}")
end
end
#
end
else
log_warn("Invalid operation (ignoring): #{the_operation[:operation].inspect}")
end
else
raise Exception, "Invalid formatted operation passed (MUST be a Hash): #{the_operation.inspect}"
end
end
#
else
raise Exception, "Database not available."
end
else
raise Exception, "Attempted to access a defunct database."
end
rescue Exception => the_error
clean_up_list.each do |the_object|
if the_object.alive?
the_object.release_reservation()
the_object.deactivate()
end
end
result[:status] = :failed
#log_error({:error => the_error, :parameters => {:records => (the_records), :options => (options)}})
log_error({:error => the_error, :parameters => {}})
end
result[:formats_needed] = formats_needed
result[:formats_provided] = formats_provided
result[:record_updates] = updates_provided
result[:records_processed] = total_records_processed
result[:formats_processed] = total_formats_processed
result
end
#
def sync_export(credential=nil, uuid_list=[], options={})
result = {:formats => {}, :records => []}
if uuid_list.is_any?(::Array, ::GxG::Database::PersistedArray)
uuid_list.each do |the_reference|
the_uuid = the_reference
if ::GxG::valid_uuid?(the_uuid)
the_object = self.retrieve_by_uuid(the_uuid, credential)
if the_object
object_record = the_object.export()
object_record.search do |item,selector,container|
if selector == :format || selector == :constraint
if item.to_s.size > 0
format_uuid = item
unless result[:formats][(format_uuid.to_s.to_sym)].is_a?(::Hash)
format_sample = self.format_load({:uuid => format_uuid.to_s.to_sym})
format_sample[:content] = format_sample[:content].gxg_export()
result[:formats][(format_uuid.to_s.to_sym)] = format_sample
end
end
end
end
result[:records] << object_record
#
end
end
end
end
result
end
#
def sync_import(credential=nil, the_records={:formats => {}, :records => []}, options={})
result = false
#
if the_records.is_a?(::Hash)
if the_records[:formats].is_a?(::Hash)
op_frame = {:operation => :merge_format, :data => []}
the_records[:formats].each_pair do |the_uuid, the_format_record|
if the_format_record[:version].is_a?(::String)
the_format_record[:version] = ::BigDecimal.new(the_format_record[:version])
end
# gxg_import format content prior to sync update op
the_format_record[:content] = ::Hash.gxg_import(the_format_record[:content])
op_frame[:data] << the_format_record
end
if op_frame[:data].size > 0
self.synchronize_records([(op_frame)],credential)
end
end
#
if the_records[:records].is_a?(::Array)
if the_records[:records].size > 0
the_records[:records].each do |the_record|
the_record.search do |value, selector, container|
if selector == :version && value.is_a?(::String)
container[(selector)] = ::BigDecimal.new(value)
end
end
end
self.synchronize_records([{:operation => :merge, :data => the_records[:records]}],credential)
result = true
end
end
end
#
result
end
# Class method for importing an export record as a detached structure.
def self.detached_import(import_record=nil)
result = nil
unless import_record.is_any?(::Hash, ::GxG::Database::PersistedHash)
raise Exception, "Malformed import record passed."
end
# {:type => :element_hash, :uuid => @uuid.clone, :title => @title.clone, :version => @version.clone, :format => @format.clone, :content => {}}
import_db = [{:target => ::GxG::Database::DetachedHash.new, :record => import_record}]
result = import_db[0][:target]
while import_db.size > 0 do
entry = import_db.shift
case entry[:record][:type].to_s.to_sym
when :element_hash
entry[:target].uuid = entry[:record][:uuid].to_s.to_sym
entry[:target].title = entry[:record][:title].to_s
entry[:record][:content].keys.each do |the_key|
case entry[:record][:content][(the_key)][:type].to_s.to_sym
when :element_hash
new_target = GxG::Database::DetachedHash.new
entry[:target][(the_key)] = new_target
import_db << {:target => new_target, :record => (entry[:record][:content][(the_key)])}
entry[:target][(the_key)].parent = (entry[:target])
when :element_array
new_target = GxG::Database::DetachedArray.new
entry[:target][(the_key)] = new_target
import_db << {:target => new_target, :record => (entry[:record][:content][(the_key)])}
entry[:target][(the_key)].parent = (entry[:target])
when :element_boolean, :element_integer, :element_float, :element_text
entry[:target][(the_key)] = entry[:record][:content][(the_key)][:content]
when :element_bigdecimal
entry[:target][(the_key)] = ::BigDecimal.new(entry[:record][:content][(the_key)][:content].to_s)
when :element_datetime
entry[:target][(the_key)] = ::Chronic::parse(entry[:record][:content][(the_key)][:content].to_s)
when :element_binary
entry[:target][(the_key)] = ::GxG::ByteArray.new(entry[:record][:content][(the_key)][:content].to_s.decode64)
end
end
entry[:target].version = ::BigDecimal.new(entry[:record][:version].to_s)
entry[:target].format = entry[:record][:format].to_s.to_sym
when :element_array
entry[:target].uuid = entry[:record][:uuid].to_s.to_sym
entry[:target].title = entry[:record][:title].to_s
entry[:record][:content].each_index do |indexer|
case entry[:record][:content][(indexer)][:type].to_s.to_sym
when :element_hash
new_target = GxG::Database::DetachedHash.new
entry[:target][(indexer)] = new_target
import_db << {:target => new_target, :record => (entry[:record][:content][(indexer)])}
entry[:target][(indexer)].parent =(entry[:target])
when :element_array
new_target = GxG::Database::DetachedArray.new
entry[:target][(indexer)] = new_target
import_db << {:target => new_target, :record => (entry[:record][:content][(indexer)])}
entry[:target][(indexer)].parent = (entry[:target])
when :element_boolean, :element_integer, :element_float, :element_text
entry[:target][(indexer)] = entry[:record][:content][(indexer)][:content]
when :element_bigdecimal
entry[:target][(indexer)] = ::BigDecimal.new(entry[:record][:content][(indexer)][:content].to_s)
when :element_datetime
entry[:target][(indexer)] = ::Chronic::parse(entry[:record][:content][(indexer)][:content].to_s)
when :element_binary
entry[:target][(indexer)] = ::GxG::ByteArray.new(entry[:record][:content][(indexer)][:content].to_s.decode64)
end
end
entry[:target].version = ::BigDecimal.new(entry[:record][:version].to_s)
entry[:target].constraint = entry[:record][:constraint].to_s.to_sym
end
end
#
result
end
# Class method to import an export package as a detached object.
def self.detached_package_import(the_records={:formats => {}, :records => []}, options={})
result = []
#
if the_records.is_a?(::Hash)
if the_records[:formats].is_a?(::Hash)
op_frame = {:operation => :merge_format, :data => []}
the_records[:formats].each_pair do |the_uuid, the_format_record|
if the_format_record[:version].is_a?(::String)
the_format_record[:version] = ::BigDecimal.new(the_format_record[:version])
end
# gxg_import format content prior to sync update op
the_format_record[:content] = ::Hash.gxg_import(the_format_record[:content])
# Add to ::GxG::DB[:formats] ??
unless ::GxG::DB[:formats][(the_uuid.to_s.to_sym)]
# ?? Review : thread safety ??
::GxG::DB[:formats][(the_uuid.to_s.to_sym)] = the_format_record
end
op_frame[:data] << the_format_record
end
if op_frame[:data].size > 0
::GxG::DB[:roles][:formats].synchronize_records([(op_frame)],::GxG::DB[:administrator])
end
end
#
if the_records[:records].is_a?(::Array)
if the_records[:records].size > 0
the_records[:records].each do |the_record|
the_record.search do |value, selector, container|
if selector == :version && value.is_a?(::String)
container[(selector)] = ::BigDecimal.new(value)
end
end
#
result << ::GxG::Database::detached_import(the_record)
#
end
end
end
end
#
result
end
#
# ### Utility methods
def clear_all_element_locks()
# for when boo-boos happen
if self.open?()
if self.db_permissions()[:write]
@connector[:element_locks].filter({}).delete
end
end
end
#
# ### Various methods
# Comm objects cannot be serialized - return encoded nil
def serialize()
"marshal:BAgw"
end
#
# Comm objects cannot be cloned - returns nil
def initialize_clone
nil
end
alias :initialize_dup :initialize_clone
alias :dup :initialize_clone
def clone()
initialize_clone
end
#
def initialize(connection = nil, options={})
# TODO: require :credential (access privs)
super()
@credential = nil
@url = options[:url]
@base_permissions = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}
#
if connection.is_a?(::Sequel::Database)
@connector = connection
#
@active = false
unless open?()
raise Exception, "Unable to connect with the database"
end
#
@scheme = @url.scheme.to_s.to_sym
case @scheme
when :sqlite, :sqlite3
@connector.run("PRAGMA page_size=65536")
@connector.run("PRAGMA max_page_count=281474976710656")
@connector.run("PRAGMA encoding='UTF-8'")
when :mysql, :mysql2
@connector.run("SET GLOBAL max_allowed_packet=17825792;")
@connector.run("SET NAMES 'utf8' COLLATE 'utf8_general_ci';")
@connector.run("SET collation_connection = 'utf8_general_ci';")
when :postgres
# place holder
end
else
raise Exception, "Invalid db connection"
end
# characterize basic effective permissions
begin
formatted = db_formatted?()
@base_permissions[:read] = true
@base_permissions[:execute] = true
rescue Exception => the_error
formatted = false
raise Exception, "Lack read permissions to the database"
end
#
unless options[:read_only]
if formatted
begin
dbid = @connector[:text_page].insert()
rescue Exception => the_error
dbid = nil
end
if dbid.is_a?(::Integer)
@connector[:text_page].filter({:dbid => (dbid)}).delete
@base_permissions[:write] = true
@base_permissions[:create] = true
@base_permissions[:destroy] = true
@base_permissions[:rename] = true
@base_permissions[:move] = true
end
else
begin
test_table_name = ::GxG::uuid_generate().gsub("-","")
while (@connector.table_exists?(test_table_name.to_sym)) do
test_table_name = ::GxG::uuid_generate().gsub("-","")
end
@connector.create_table(test_table_name.to_sym) do
primary_key :dbid
end
@connector.drop_table(test_table_name.to_sym)
@base_permissions[:write] = true
@base_permissions[:create] = true
@base_permissions[:destroy] = true
@base_permissions[:rename] = true
@base_permissions[:move] = true
rescue Exception => the_error
# Read-only Database
end
end
end
#
@base_permissions.freeze
#
unless formatted
if @base_permissions[:write]
db_format
unless db_formatted?()
@active = false
raise Exception, "Unable to format the database for use"
end
else
raise Exception, "Unable to format a read-only database"
end
end
#
@authority = nil
@db_list = []
if options[:authority].is_any?(::GxG::Database::Database, ::GxG::Database::LDAPAuthority)
@authority = options[:authority]
@authority.db_register(self)
end
#
unless @authority
unless self.role_exist?("Administrators")
if @base_permissions[:write]
if self.user_id_available?("root")
self.user_create("root","password")
temp_cred = self.user_credential("root","password")
temp_group = self.role_create("Administrators")
self.role_add_user(temp_group,temp_cred)
#
dev_group = self.role_create("Developers")
dsn_group = self.role_create("Designers")
usr_group = self.role_create("Users")
#
self[:system_credentials] = {:administrator => temp_cred, :administrators => temp_group, :developers => dev_group, :designers => dsn_group, :users => usr_group}
self[:permission_policy] = [{:event => :extend, :credential => :any, :action => {:extend_permissions => temp_group}}]
#
end
if self.user_id_available?("public")
self.user_create("public","password", "00000000-0000-4000-0000-000000000000")
end
end
end
end
#
permission_policy_load
self
end
#
end
#
end
#
end
#
require File.expand_path("./gxg_database_persistedarray.rb",File.dirname(__FILE__))
require File.expand_path("./gxg_database_persistedhash.rb",File.dirname(__FILE__))
#
class Hash
#
def persist(credential=::GxG::DB[:administrator], to_db=:data)
result = nil
database = ::GxG::DB[:roles][(to_db)]
if database.is_a?(::GxG::Database::Database)
result = database.try_persist(self, credential)
end
result
end
#
end
#
class Array
#
def persist(credential=::GxG::DB[:administrator], to_db=:data)
result = nil
database = ::GxG::DB[:roles][(to_db)]
if database.is_a?(::GxG::Database::Database)
result = database.try_persist(self, credential)
end
result
end
#
end