carton_db 1.1.3 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +25 -32
- data/lib/carton_db.rb +4 -0
- data/lib/carton_db/escaping.rb +29 -11
- data/lib/carton_db/list_map_db.rb +7 -11
- data/lib/carton_db/list_map_db/segment.rb +39 -30
- data/lib/carton_db/version.rb +1 -1
- metadata +2 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: ce6b3966ce215081d2036a69277b65cd3958d0ff
         | 
| 4 | 
            +
              data.tar.gz: 62f46c6218f8a938a60d6a7b67a0d7314b07f43e
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 61c8a47573f2f015934f797309381c250cf7cd96ab00a2a244c6c0ae287c3b75bc90a36c2643b76ecd071ac6103c0c39403d7d7a3e0ded63f4e6836a0244be83
         | 
| 7 | 
            +
              data.tar.gz: d8ceaa939dcc182dced7fc4e41e896419eac34625598c68a6ddd7b2007b66ca7a12e1be3136aa0cdb1309c115b177620888df457735045af3e9ad426d56c1c67
         | 
    
        data/README.md
    CHANGED
    
    | @@ -7,28 +7,17 @@ The primary goals of this library are simplicity of implementation | |
| 7 7 | 
             
            and reliable, predictable behavior when used as intended, along
         | 
| 8 8 | 
             
            with documentation making it reasonably clear what is intended.
         | 
| 9 9 |  | 
| 10 | 
            +
            Secondarily, this library is optimized for fast appending to
         | 
| 11 | 
            +
            existing entries.
         | 
| 12 | 
            +
             | 
| 10 13 | 
             
            ## Uses
         | 
| 11 14 |  | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
            sets of elements that would be too large to be effectively
         | 
| 19 | 
            -
            handled in memory. The application didn't already have any use
         | 
| 20 | 
            -
            for a relational database server, and I didn't want to add one
         | 
| 21 | 
            -
            just for this requirement. A Redis db with sufficient capacity
         | 
| 22 | 
            -
            would have been expensive, and solutions such as SQLite are
         | 
| 23 | 
            -
            specifically not supported by Heroku so people don't mistakenly
         | 
| 24 | 
            -
            expect the data to be preserved. Ruby's `PStore`, `DBM` and
         | 
| 25 | 
            -
            `SDMB` each proved to be too unpredicatable and flakey to be a
         | 
| 26 | 
            -
            practical solution.
         | 
| 27 | 
            -
             | 
| 28 | 
            -
            Although this tool was initially developed to store transient
         | 
| 29 | 
            -
            data for use within a single process invocation and then
         | 
| 30 | 
            -
            discarded, it is also well suited for long term data storage on a
         | 
| 31 | 
            -
            system that retains filesystem contents.
         | 
| 15 | 
            +
            This library is useful in some of the same situations in which
         | 
| 16 | 
            +
            one might consider using the `PStore`, `DBM`, or `SMDB` classes
         | 
| 17 | 
            +
            provided as part of Ruby's standard library, but you either need
         | 
| 18 | 
            +
            something more solid and trustworthy or you need something that
         | 
| 19 | 
            +
            supports fast appending of elements to lists or sets within
         | 
| 20 | 
            +
            entries.
         | 
| 32 21 |  | 
| 33 22 | 
             
            ## Installation
         | 
| 34 23 |  | 
| @@ -53,13 +42,13 @@ filesystem containing the files that store the data. | |
| 53 42 |  | 
| 54 43 | 
             
            A database is accessed through an instance of a database class.
         | 
| 55 44 |  | 
| 56 | 
            -
            An instance of a database class  | 
| 57 | 
            -
            between calls to its methods  | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 45 | 
            +
            An instance of a database class assumes nothing about the state
         | 
| 46 | 
            +
            of the database between calls to its methods, and only expects
         | 
| 47 | 
            +
            that the database exists and is valid when a call is made that
         | 
| 48 | 
            +
            reads or writes data.
         | 
| 60 49 |  | 
| 61 | 
            -
            Only instances of classes maintain any internal state. | 
| 62 | 
            -
            internal state is maintained.
         | 
| 50 | 
            +
            Only instances of database classes maintain any internal state.
         | 
| 51 | 
            +
            No global internal state is maintained.
         | 
| 63 52 |  | 
| 64 53 | 
             
            An empty directory is a valid empty database.
         | 
| 65 54 |  | 
| @@ -76,11 +65,14 @@ will be raised if it doesn't. | |
| 76 65 |  | 
| 77 66 | 
             
            The database structure is designed to effectively handle up to
         | 
| 78 67 | 
             
            several million elements with any entry containing up to around
         | 
| 79 | 
            -
            50 thousand characters ( | 
| 80 | 
            -
             | 
| 81 | 
            -
            The speed of database operations is good,  | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 68 | 
            +
            50 thousand characters (in all of the entry's elements combined).
         | 
| 69 | 
            +
             | 
| 70 | 
            +
            The speed of database operations is good, and it is particularly
         | 
| 71 | 
            +
            optimized for appending to new or existing entries. It was not
         | 
| 72 | 
            +
            designed or optimized to be a "high performance" database
         | 
| 73 | 
            +
            management system though, and it has not been benchmarked against
         | 
| 74 | 
            +
            other systems for specific performance comparison. See the inline
         | 
| 75 | 
            +
            code documentation of the classes for details about the
         | 
| 84 76 | 
             
            performance of each kind of database operation.
         | 
| 85 77 |  | 
| 86 78 | 
             
            ## Usage
         | 
| @@ -89,7 +81,8 @@ The primary kind of database provided by this gem is the one | |
| 89 81 | 
             
            implemented by `CartonDB::ListMapDb`. It is a map of lists where
         | 
| 90 82 | 
             
            each entry has a string for a key and a list of of 0 or more
         | 
| 91 83 | 
             
            string elements as content. Other kinds of database are
         | 
| 92 | 
            -
            implemented  | 
| 84 | 
            +
            implemented as specializations of that and share the same storage
         | 
| 85 | 
            +
            format.
         | 
| 93 86 |  | 
| 94 87 | 
             
            The name of the database is the path of a directory in the
         | 
| 95 88 | 
             
            filesystem that either already exists or shall be created as
         | 
    
        data/lib/carton_db.rb
    CHANGED
    
    | @@ -9,4 +9,8 @@ require "carton_db/simple_map_db" | |
| 9 9 | 
             
            require "carton_db/set_map_db"
         | 
| 10 10 |  | 
| 11 11 | 
             
            module CartonDb
         | 
| 12 | 
            +
              Error = Class.new(StandardError)
         | 
| 13 | 
            +
              UnescapingError = Class.new(Error)
         | 
| 14 | 
            +
              InvalidEscapeSequence = Class.new(UnescapingError)
         | 
| 15 | 
            +
              IncompleteEscapeSequence = Class.new(UnescapingError)
         | 
| 12 16 | 
             
            end
         | 
    
        data/lib/carton_db/escaping.rb
    CHANGED
    
    | @@ -44,18 +44,36 @@ module CartonDb | |
| 44 44 |  | 
| 45 45 | 
             
                UNESCAPING_MAP = ESCAPING_MAP.invert.freeze
         | 
| 46 46 |  | 
| 47 | 
            -
                 | 
| 48 | 
            -
                  value | 
| 49 | 
            -
                     | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 47 | 
            +
                class << self
         | 
| 48 | 
            +
                  def escape(value)
         | 
| 49 | 
            +
                    value.gsub(
         | 
| 50 | 
            +
                      /[\x00-\x1F\x7F\\]/,
         | 
| 51 | 
            +
                      ESCAPING_MAP
         | 
| 52 | 
            +
                    )
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  def unescape(esc)
         | 
| 56 | 
            +
                    esc.gsub( /\\(?:\\|x[01][0-9A-F]|x7F|[^x\\]|$)/ ) { |match|
         | 
| 57 | 
            +
                      UNESCAPING_MAP.fetch match do
         | 
| 58 | 
            +
                        incomplete_sequence! match if match == "\\"
         | 
| 59 | 
            +
                        invalid_sequence! match
         | 
| 60 | 
            +
                      end
         | 
| 61 | 
            +
                    }
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  private
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  def incomplete_sequence!(sequence)
         | 
| 67 | 
            +
                    message =
         | 
| 68 | 
            +
                      "Escaped text contains incomplete escape sequence %s" % sequence
         | 
| 69 | 
            +
                    raise CartonDb::IncompleteEscapeSequence, message
         | 
| 70 | 
            +
                  end
         | 
| 53 71 |  | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
                     | 
| 58 | 
            -
                   | 
| 72 | 
            +
                  def invalid_sequence!(sequence)
         | 
| 73 | 
            +
                    message =
         | 
| 74 | 
            +
                      "Escaped text contains invalid escape sequence %s" % sequence
         | 
| 75 | 
            +
                    raise CartonDb::InvalidEscapeSequence, message
         | 
| 76 | 
            +
                  end
         | 
| 59 77 | 
             
                end
         | 
| 60 78 |  | 
| 61 79 | 
             
              end
         | 
| @@ -217,10 +217,8 @@ module CartonDb | |
| 217 217 | 
             
                  segment = segment_containing(key_d)
         | 
| 218 218 | 
             
                  return if segment.empty?
         | 
| 219 219 |  | 
| 220 | 
            -
                  segment.replace do | | 
| 221 | 
            -
                     | 
| 222 | 
            -
                      segment.copy_entries_except key_d, repl_io
         | 
| 223 | 
            -
                    end
         | 
| 220 | 
            +
                  segment.replace do |repl_io|
         | 
| 221 | 
            +
                    segment.copy_entries_except key_d, repl_io
         | 
| 224 222 | 
             
                  end
         | 
| 225 223 | 
             
                end
         | 
| 226 224 |  | 
| @@ -325,13 +323,11 @@ module CartonDb | |
| 325 323 | 
             
                attr_accessor :name
         | 
| 326 324 |  | 
| 327 325 | 
             
                def replace_entry_in_file(segment, key_d, content)
         | 
| 328 | 
            -
                  segment.replace do | | 
| 329 | 
            -
                     | 
| 330 | 
            -
             | 
| 331 | 
            -
             | 
| 332 | 
            -
             | 
| 333 | 
            -
                      repl_io << "#{key_d.escaped}\n" if count.zero?
         | 
| 334 | 
            -
                    end
         | 
| 326 | 
            +
                  segment.replace do |repl_io|
         | 
| 327 | 
            +
                    segment.copy_entries_except key_d, repl_io
         | 
| 328 | 
            +
                    element_count = 0
         | 
| 329 | 
            +
                    count = write_key_elements(key_d, content, repl_io)
         | 
| 330 | 
            +
                    repl_io << "#{key_d.escaped}\n" if count.zero?
         | 
| 335 331 | 
             
                  end
         | 
| 336 332 | 
             
                end
         | 
| 337 333 |  | 
| @@ -67,11 +67,7 @@ module CartonDb | |
| 67 67 | 
             
                  end
         | 
| 68 68 |  | 
| 69 69 | 
             
                  def touch_d(key_d, optimization)
         | 
| 70 | 
            -
                    if optimization == :small &&  | 
| 71 | 
            -
                      each_entry_element_line do |kd, _ed, _line|
         | 
| 72 | 
            -
                        return if kd == key_d
         | 
| 73 | 
            -
                      end
         | 
| 74 | 
            -
                    end
         | 
| 70 | 
            +
                    return if optimization == :small && key_d?(key_d)
         | 
| 75 71 |  | 
| 76 72 | 
             
                    open_append do |io|
         | 
| 77 73 | 
             
                      io << key_d.escaped << "\n"
         | 
| @@ -102,8 +98,7 @@ module CartonDb | |
| 102 98 |  | 
| 103 99 | 
             
                  def collect_content(key_d, collection_class)
         | 
| 104 100 | 
             
                    result = nil
         | 
| 105 | 
            -
                     | 
| 106 | 
            -
                      next unless kd == key_d
         | 
| 101 | 
            +
                    each_element_for_d key_d do |ed|
         | 
| 107 102 | 
             
                      result ||= collection_class.new
         | 
| 108 103 | 
             
                      result << ed.plain unless ed.placeholder?
         | 
| 109 104 | 
             
                    end
         | 
| @@ -111,12 +106,7 @@ module CartonDb | |
| 111 106 | 
             
                  end
         | 
| 112 107 |  | 
| 113 108 | 
             
                  def each_entry
         | 
| 114 | 
            -
                    entries =  | 
| 115 | 
            -
                    each_entry_element_line do |key_d, elem_d, _line|
         | 
| 116 | 
            -
                      entries ||= {}
         | 
| 117 | 
            -
                      content = entries[key_d] ||= []
         | 
| 118 | 
            -
                      content << elem_d.plain unless elem_d.placeholder?
         | 
| 119 | 
            -
                    end
         | 
| 109 | 
            +
                    entries = key_d_contents_map
         | 
| 120 110 | 
             
                    return unless entries
         | 
| 121 111 | 
             
                    entries.each do |key_d, content|
         | 
| 122 112 | 
             
                      yield key_d.plain, content
         | 
| @@ -131,13 +121,10 @@ module CartonDb | |
| 131 121 | 
             
                  end
         | 
| 132 122 |  | 
| 133 123 | 
             
                  def each_first_element
         | 
| 134 | 
            -
                     | 
| 135 | 
            -
                     | 
| 136 | 
            -
             | 
| 137 | 
            -
             | 
| 138 | 
            -
                    end
         | 
| 139 | 
            -
                    return unless first_entries
         | 
| 140 | 
            -
                    first_entries.each do |key_d, element|
         | 
| 124 | 
            +
                    first_element_map = key_d_first_element_map
         | 
| 125 | 
            +
                    return unless first_element_map
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                    first_element_map.each do |key_d, element|
         | 
| 141 128 | 
             
                      yield key_d.plain, element
         | 
| 142 129 | 
             
                    end
         | 
| 143 130 | 
             
                  end
         | 
| @@ -154,11 +141,12 @@ module CartonDb | |
| 154 141 | 
             
                  end
         | 
| 155 142 |  | 
| 156 143 | 
             
                  def replace
         | 
| 157 | 
            -
                     | 
| 158 | 
            -
             | 
| 159 | 
            -
                    )
         | 
| 144 | 
            +
                    repl_name = "#{segment_filename}.temp"
         | 
| 145 | 
            +
                    replacement = self.class.new(segment_group, repl_name)
         | 
| 160 146 | 
             
                    begin
         | 
| 161 | 
            -
                       | 
| 147 | 
            +
                      replacement.open_overwrite do |io|
         | 
| 148 | 
            +
                        yield io
         | 
| 149 | 
            +
                      end
         | 
| 162 150 | 
             
                    rescue StandardError
         | 
| 163 151 | 
             
                      File.unlink replacement.filename
         | 
| 164 152 | 
             
                      raise
         | 
| @@ -188,12 +176,7 @@ module CartonDb | |
| 188 176 | 
             
                    end
         | 
| 189 177 | 
             
                  end
         | 
| 190 178 |  | 
| 191 | 
            -
                   | 
| 192 | 
            -
                    touch_dir
         | 
| 193 | 
            -
                    File.open filename, 'a', **FILE_ENCODING_OPTS do |io|
         | 
| 194 | 
            -
                      yield io
         | 
| 195 | 
            -
                    end
         | 
| 196 | 
            -
                  end
         | 
| 179 | 
            +
                  protected
         | 
| 197 180 |  | 
| 198 181 | 
             
                  def open_overwrite
         | 
| 199 182 | 
             
                    touch_dir
         | 
| @@ -216,6 +199,13 @@ module CartonDb | |
| 216 199 | 
             
                    end
         | 
| 217 200 | 
             
                  end
         | 
| 218 201 |  | 
| 202 | 
            +
                  def open_append
         | 
| 203 | 
            +
                    touch_dir
         | 
| 204 | 
            +
                    File.open filename, 'a', **FILE_ENCODING_OPTS do |io|
         | 
| 205 | 
            +
                      yield io
         | 
| 206 | 
            +
                    end
         | 
| 207 | 
            +
                  end
         | 
| 208 | 
            +
             | 
| 219 209 | 
             
                  def touch_dir
         | 
| 220 210 | 
             
                    dir = File.dirname(filename)
         | 
| 221 211 | 
             
                    return if File.directory?(dir)
         | 
| @@ -230,6 +220,25 @@ module CartonDb | |
| 230 220 | 
             
                    end
         | 
| 231 221 | 
             
                  end
         | 
| 232 222 |  | 
| 223 | 
            +
                  def key_d_contents_map
         | 
| 224 | 
            +
                    entries = nil
         | 
| 225 | 
            +
                    each_entry_element_line do |key_d, elem_d, _line|
         | 
| 226 | 
            +
                      entries ||= {}
         | 
| 227 | 
            +
                      content = entries[key_d] ||= []
         | 
| 228 | 
            +
                      content << elem_d.plain unless elem_d.placeholder?
         | 
| 229 | 
            +
                    end
         | 
| 230 | 
            +
                    entries
         | 
| 231 | 
            +
                  end
         | 
| 232 | 
            +
             | 
| 233 | 
            +
                  def key_d_first_element_map
         | 
| 234 | 
            +
                    result = nil
         | 
| 235 | 
            +
                    each_entry_element_line do |key_d, elem_d, _line|
         | 
| 236 | 
            +
                      result ||= {}
         | 
| 237 | 
            +
                      result[key_d] ||= elem_d.plain
         | 
| 238 | 
            +
                    end
         | 
| 239 | 
            +
                    return result
         | 
| 240 | 
            +
                  end
         | 
| 241 | 
            +
             | 
| 233 242 | 
             
                end
         | 
| 234 243 |  | 
| 235 244 | 
             
              end
         | 
    
        data/lib/carton_db/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: carton_db
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1.1. | 
| 4 | 
            +
              version: 1.1.4
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Steve Jorgensen
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2017-05- | 
| 11 | 
            +
            date: 2017-05-17 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: bundler
         |