extralite-bundle 2.4 → 2.6
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/.github/workflows/test-bundle.yml +30 -0
- data/.github/workflows/test.yml +2 -12
- data/CHANGELOG.md +49 -10
- data/Gemfile.lock +1 -1
- data/LICENSE +1 -1
- data/README.md +876 -217
- data/TODO.md +2 -3
- data/ext/extralite/changeset.c +463 -0
- data/ext/extralite/common.c +226 -19
- data/ext/extralite/database.c +339 -23
- data/ext/extralite/extconf-bundle.rb +10 -4
- data/ext/extralite/extconf.rb +31 -27
- data/ext/extralite/extralite.h +25 -5
- data/ext/extralite/extralite_ext.c +10 -0
- data/ext/extralite/iterator.c +8 -3
- data/ext/extralite/query.c +222 -22
- data/ext/sqlite3/sqlite3.c +5420 -2501
- data/ext/sqlite3/sqlite3.h +73 -18
- data/gemspec.rb +1 -1
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +64 -8
- data/test/helper.rb +8 -0
- data/test/issue-54.rb +21 -0
- data/test/issue-59.rb +70 -0
- data/test/perf_ary.rb +14 -12
- data/test/perf_hash.rb +17 -15
- data/test/perf_hash_prepared.rb +58 -0
- data/test/test_changeset.rb +161 -0
- data/test/test_database.rb +672 -13
- data/test/test_query.rb +367 -2
- metadata +10 -5
- data/test/perf_prepared.rb +0 -64
    
        data/ext/sqlite3/sqlite3.h
    CHANGED
    
    | @@ -146,9 +146,9 @@ extern "C" { | |
| 146 146 | 
             
            ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
         | 
| 147 147 | 
             
            ** [sqlite_version()] and [sqlite_source_id()].
         | 
| 148 148 | 
             
            */
         | 
| 149 | 
            -
            #define SQLITE_VERSION        "3. | 
| 150 | 
            -
            #define SQLITE_VERSION_NUMBER  | 
| 151 | 
            -
            #define SQLITE_SOURCE_ID      " | 
| 149 | 
            +
            #define SQLITE_VERSION        "3.45.0"
         | 
| 150 | 
            +
            #define SQLITE_VERSION_NUMBER 3045000
         | 
| 151 | 
            +
            #define SQLITE_SOURCE_ID      "2024-01-15 17:01:13 1066602b2b1976fe58b5150777cced894af17c803e068f5918390d6915b46e1d"
         | 
| 152 152 |  | 
| 153 153 | 
             
            /*
         | 
| 154 154 | 
             
            ** CAPI3REF: Run-Time Library Version Numbers
         | 
| @@ -3954,15 +3954,17 @@ SQLITE_API void sqlite3_free_filename(sqlite3_filename); | |
| 3954 3954 | 
             
            ** </ul>
         | 
| 3955 3955 | 
             
            **
         | 
| 3956 3956 | 
             
            ** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language
         | 
| 3957 | 
            -
            ** text that describes the error, as either UTF-8 or UTF-16 respectively | 
| 3957 | 
            +
            ** text that describes the error, as either UTF-8 or UTF-16 respectively,
         | 
| 3958 | 
            +
            ** or NULL if no error message is available.
         | 
| 3958 3959 | 
             
            ** (See how SQLite handles [invalid UTF] for exceptions to this rule.)
         | 
| 3959 3960 | 
             
            ** ^(Memory to hold the error message string is managed internally.
         | 
| 3960 3961 | 
             
            ** The application does not need to worry about freeing the result.
         | 
| 3961 3962 | 
             
            ** However, the error string might be overwritten or deallocated by
         | 
| 3962 3963 | 
             
            ** subsequent calls to other SQLite interface functions.)^
         | 
| 3963 3964 | 
             
            **
         | 
| 3964 | 
            -
            ** ^The sqlite3_errstr() interface returns the English-language text
         | 
| 3965 | 
            -
            ** that describes the [result code], as UTF-8 | 
| 3965 | 
            +
            ** ^The sqlite3_errstr(E) interface returns the English-language text
         | 
| 3966 | 
            +
            ** that describes the [result code] E, as UTF-8, or NULL if E is not an
         | 
| 3967 | 
            +
            ** result code for which a text error message is available.
         | 
| 3966 3968 | 
             
            ** ^(Memory to hold the error message string is managed internally
         | 
| 3967 3969 | 
             
            ** and must not be freed by the application)^.
         | 
| 3968 3970 | 
             
            **
         | 
| @@ -8037,9 +8039,11 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); | |
| 8037 8039 | 
             
            **
         | 
| 8038 8040 | 
             
            ** ^(Some systems (for example, Windows 95) do not support the operation
         | 
| 8039 8041 | 
             
            ** implemented by sqlite3_mutex_try().  On those systems, sqlite3_mutex_try()
         | 
| 8040 | 
            -
            ** will always return SQLITE_BUSY.  | 
| 8041 | 
            -
            ** sqlite3_mutex_try() as an optimization so this is acceptable
         | 
| 8042 | 
            -
            ** behavior. | 
| 8042 | 
            +
            ** will always return SQLITE_BUSY. In most cases the SQLite core only uses
         | 
| 8043 | 
            +
            ** sqlite3_mutex_try() as an optimization, so this is acceptable
         | 
| 8044 | 
            +
            ** behavior. The exceptions are unix builds that set the
         | 
| 8045 | 
            +
            ** SQLITE_ENABLE_SETLK_TIMEOUT build option. In that case a working
         | 
| 8046 | 
            +
            ** sqlite3_mutex_try() is required.)^
         | 
| 8043 8047 | 
             
            **
         | 
| 8044 8048 | 
             
            ** ^The sqlite3_mutex_leave() routine exits a mutex that was
         | 
| 8045 8049 | 
             
            ** previously entered by the same thread.   The behavior
         | 
| @@ -8298,6 +8302,7 @@ SQLITE_API int sqlite3_test_control(int op, ...); | |
| 8298 8302 | 
             
            #define SQLITE_TESTCTRL_ASSERT                  12
         | 
| 8299 8303 | 
             
            #define SQLITE_TESTCTRL_ALWAYS                  13
         | 
| 8300 8304 | 
             
            #define SQLITE_TESTCTRL_RESERVE                 14  /* NOT USED */
         | 
| 8305 | 
            +
            #define SQLITE_TESTCTRL_JSON_SELFCHECK          14
         | 
| 8301 8306 | 
             
            #define SQLITE_TESTCTRL_OPTIMIZATIONS           15
         | 
| 8302 8307 | 
             
            #define SQLITE_TESTCTRL_ISKEYWORD               16  /* NOT USED */
         | 
| 8303 8308 | 
             
            #define SQLITE_TESTCTRL_SCRATCHMALLOC           17  /* NOT USED */
         | 
| @@ -12811,8 +12816,11 @@ struct Fts5PhraseIter { | |
| 12811 12816 | 
             
            **   created with the "columnsize=0" option.
         | 
| 12812 12817 | 
             
            **
         | 
| 12813 12818 | 
             
            ** xColumnText:
         | 
| 12814 | 
            -
            **    | 
| 12815 | 
            -
            **    | 
| 12819 | 
            +
            **   If parameter iCol is less than zero, or greater than or equal to the
         | 
| 12820 | 
            +
            **   number of columns in the table, SQLITE_RANGE is returned.
         | 
| 12821 | 
            +
            **
         | 
| 12822 | 
            +
            **   Otherwise, this function attempts to retrieve the text of column iCol of
         | 
| 12823 | 
            +
            **   the current document. If successful, (*pz) is set to point to a buffer
         | 
| 12816 12824 | 
             
            **   containing the text in utf-8 encoding, (*pn) is set to the size in bytes
         | 
| 12817 12825 | 
             
            **   (not characters) of the buffer and SQLITE_OK is returned. Otherwise,
         | 
| 12818 12826 | 
             
            **   if an error occurs, an SQLite error code is returned and the final values
         | 
| @@ -12822,8 +12830,10 @@ struct Fts5PhraseIter { | |
| 12822 12830 | 
             
            **   Returns the number of phrases in the current query expression.
         | 
| 12823 12831 | 
             
            **
         | 
| 12824 12832 | 
             
            ** xPhraseSize:
         | 
| 12825 | 
            -
            **    | 
| 12826 | 
            -
            **    | 
| 12833 | 
            +
            **   If parameter iCol is less than zero, or greater than or equal to the
         | 
| 12834 | 
            +
            **   number of phrases in the current query, as returned by xPhraseCount,
         | 
| 12835 | 
            +
            **   0 is returned. Otherwise, this function returns the number of tokens in
         | 
| 12836 | 
            +
            **   phrase iPhrase of the query. Phrases are numbered starting from zero.
         | 
| 12827 12837 | 
             
            **
         | 
| 12828 12838 | 
             
            ** xInstCount:
         | 
| 12829 12839 | 
             
            **   Set *pnInst to the total number of occurrences of all phrases within
         | 
| @@ -12839,12 +12849,13 @@ struct Fts5PhraseIter { | |
| 12839 12849 | 
             
            **   Query for the details of phrase match iIdx within the current row.
         | 
| 12840 12850 | 
             
            **   Phrase matches are numbered starting from zero, so the iIdx argument
         | 
| 12841 12851 | 
             
            **   should be greater than or equal to zero and smaller than the value
         | 
| 12842 | 
            -
            **   output by xInstCount().
         | 
| 12852 | 
            +
            **   output by xInstCount(). If iIdx is less than zero or greater than
         | 
| 12853 | 
            +
            **   or equal to the value returned by xInstCount(), SQLITE_RANGE is returned.
         | 
| 12843 12854 | 
             
            **
         | 
| 12844 | 
            -
            **    | 
| 12855 | 
            +
            **   Otherwise, output parameter *piPhrase is set to the phrase number, *piCol
         | 
| 12845 12856 | 
             
            **   to the column in which it occurs and *piOff the token offset of the
         | 
| 12846 | 
            -
            **   first token of the phrase.  | 
| 12847 | 
            -
            **   code (i.e. SQLITE_NOMEM) if an error occurs.
         | 
| 12857 | 
            +
            **   first token of the phrase. SQLITE_OK is returned if successful, or an
         | 
| 12858 | 
            +
            **   error code (i.e. SQLITE_NOMEM) if an error occurs.
         | 
| 12848 12859 | 
             
            **
         | 
| 12849 12860 | 
             
            **   This API can be quite slow if used with an FTS5 table created with the
         | 
| 12850 12861 | 
             
            **   "detail=none" or "detail=column" option.
         | 
| @@ -12870,6 +12881,10 @@ struct Fts5PhraseIter { | |
| 12870 12881 | 
             
            **   Invoking Api.xUserData() returns a copy of the pointer passed as
         | 
| 12871 12882 | 
             
            **   the third argument to pUserData.
         | 
| 12872 12883 | 
             
            **
         | 
| 12884 | 
            +
            **   If parameter iPhrase is less than zero, or greater than or equal to
         | 
| 12885 | 
            +
            **   the number of phrases in the query, as returned by xPhraseCount(),
         | 
| 12886 | 
            +
            **   this function returns SQLITE_RANGE.
         | 
| 12887 | 
            +
            **
         | 
| 12873 12888 | 
             
            **   If the callback function returns any value other than SQLITE_OK, the
         | 
| 12874 12889 | 
             
            **   query is abandoned and the xQueryPhrase function returns immediately.
         | 
| 12875 12890 | 
             
            **   If the returned value is SQLITE_DONE, xQueryPhrase returns SQLITE_OK.
         | 
| @@ -12984,9 +12999,42 @@ struct Fts5PhraseIter { | |
| 12984 12999 | 
             
            **
         | 
| 12985 13000 | 
             
            ** xPhraseNextColumn()
         | 
| 12986 13001 | 
             
            **   See xPhraseFirstColumn above.
         | 
| 13002 | 
            +
            **
         | 
| 13003 | 
            +
            ** xQueryToken(pFts5, iPhrase, iToken, ppToken, pnToken)
         | 
| 13004 | 
            +
            **   This is used to access token iToken of phrase iPhrase of the current
         | 
| 13005 | 
            +
            **   query. Before returning, output parameter *ppToken is set to point
         | 
| 13006 | 
            +
            **   to a buffer containing the requested token, and *pnToken to the
         | 
| 13007 | 
            +
            **   size of this buffer in bytes.
         | 
| 13008 | 
            +
            **
         | 
| 13009 | 
            +
            **   If iPhrase or iToken are less than zero, or if iPhrase is greater than
         | 
| 13010 | 
            +
            **   or equal to the number of phrases in the query as reported by
         | 
| 13011 | 
            +
            **   xPhraseCount(), or if iToken is equal to or greater than the number of
         | 
| 13012 | 
            +
            **   tokens in the phrase, SQLITE_RANGE is returned and *ppToken and *pnToken
         | 
| 13013 | 
            +
                 are both zeroed.
         | 
| 13014 | 
            +
            **
         | 
| 13015 | 
            +
            **   The output text is not a copy of the query text that specified the
         | 
| 13016 | 
            +
            **   token. It is the output of the tokenizer module. For tokendata=1
         | 
| 13017 | 
            +
            **   tables, this includes any embedded 0x00 and trailing data.
         | 
| 13018 | 
            +
            **
         | 
| 13019 | 
            +
            ** xInstToken(pFts5, iIdx, iToken, ppToken, pnToken)
         | 
| 13020 | 
            +
            **   This is used to access token iToken of phrase hit iIdx within the
         | 
| 13021 | 
            +
            **   current row. If iIdx is less than zero or greater than or equal to the
         | 
| 13022 | 
            +
            **   value returned by xInstCount(), SQLITE_RANGE is returned.  Otherwise,
         | 
| 13023 | 
            +
            **   output variable (*ppToken) is set to point to a buffer containing the
         | 
| 13024 | 
            +
            **   matching document token, and (*pnToken) to the size of that buffer in
         | 
| 13025 | 
            +
            **   bytes. This API is not available if the specified token matches a
         | 
| 13026 | 
            +
            **   prefix query term. In that case both output variables are always set
         | 
| 13027 | 
            +
            **   to 0.
         | 
| 13028 | 
            +
            **
         | 
| 13029 | 
            +
            **   The output text is not a copy of the document text that was tokenized.
         | 
| 13030 | 
            +
            **   It is the output of the tokenizer module. For tokendata=1 tables, this
         | 
| 13031 | 
            +
            **   includes any embedded 0x00 and trailing data.
         | 
| 13032 | 
            +
            **
         | 
| 13033 | 
            +
            **   This API can be quite slow if used with an FTS5 table created with the
         | 
| 13034 | 
            +
            **   "detail=none" or "detail=column" option.
         | 
| 12987 13035 | 
             
            */
         | 
| 12988 13036 | 
             
            struct Fts5ExtensionApi {
         | 
| 12989 | 
            -
              int iVersion;                   /* Currently always set to  | 
| 13037 | 
            +
              int iVersion;                   /* Currently always set to 3 */
         | 
| 12990 13038 |  | 
| 12991 13039 | 
             
              void *(*xUserData)(Fts5Context*);
         | 
| 12992 13040 |  | 
| @@ -13021,6 +13069,13 @@ struct Fts5ExtensionApi { | |
| 13021 13069 |  | 
| 13022 13070 | 
             
              int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*);
         | 
| 13023 13071 | 
             
              void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol);
         | 
| 13072 | 
            +
             | 
| 13073 | 
            +
              /* Below this point are iVersion>=3 only */
         | 
| 13074 | 
            +
              int (*xQueryToken)(Fts5Context*,
         | 
| 13075 | 
            +
                  int iPhrase, int iToken,
         | 
| 13076 | 
            +
                  const char **ppToken, int *pnToken
         | 
| 13077 | 
            +
              );
         | 
| 13078 | 
            +
              int (*xInstToken)(Fts5Context*, int iIdx, int iToken, const char**, int*);
         | 
| 13024 13079 | 
             
            };
         | 
| 13025 13080 |  | 
| 13026 13081 | 
             
            /*
         | 
    
        data/gemspec.rb
    CHANGED
    
    | @@ -16,7 +16,7 @@ def common_spec(s) | |
| 16 16 | 
             
              s.rdoc_options = ["--title", "extralite", "--main", "README.md"]
         | 
| 17 17 | 
             
              s.extra_rdoc_files = ["README.md"]
         | 
| 18 18 | 
             
              s.require_paths = ["lib"]
         | 
| 19 | 
            -
              s.required_ruby_version = '>=  | 
| 19 | 
            +
              s.required_ruby_version = '>= 3.0'
         | 
| 20 20 |  | 
| 21 21 | 
             
              s.add_development_dependency  'rake-compiler',        '1.1.6'
         | 
| 22 22 | 
             
              s.add_development_dependency  'minitest',             '5.15.0'
         | 
    
        data/lib/extralite/version.rb
    CHANGED
    
    
    
        data/lib/extralite.rb
    CHANGED
    
    | @@ -33,16 +33,20 @@ module Extralite | |
| 33 33 | 
             
              class Database
         | 
| 34 34 | 
             
                # @!visibility private
         | 
| 35 35 | 
             
                TABLES_SQL = <<~SQL
         | 
| 36 | 
            -
                  SELECT name FROM sqlite_master
         | 
| 36 | 
            +
                  SELECT name FROM %<db>s.sqlite_master
         | 
| 37 37 | 
             
                  WHERE type ='table'
         | 
| 38 | 
            -
                    AND name NOT LIKE 'sqlite_ | 
| 38 | 
            +
                    AND name NOT LIKE 'sqlite_%%';
         | 
| 39 39 | 
             
                SQL
         | 
| 40 40 |  | 
| 41 | 
            -
                 | 
| 41 | 
            +
                alias_method :execute_multi, :batch_execute
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                # Returns the list of currently defined tables. If a database name is given,
         | 
| 44 | 
            +
                # returns the list of tables for the relevant attached database.
         | 
| 42 45 | 
             
                #
         | 
| 46 | 
            +
                # @param db [String] name of attached database
         | 
| 43 47 | 
             
                # @return [Array] list of tables
         | 
| 44 | 
            -
                def tables
         | 
| 45 | 
            -
                  query_single_column(TABLES_SQL)
         | 
| 48 | 
            +
                def tables(db = 'main')
         | 
| 49 | 
            +
                  query_single_column(format(TABLES_SQL, db: db))
         | 
| 46 50 | 
             
                end
         | 
| 47 51 |  | 
| 48 52 | 
             
                # Gets or sets one or more pragmas:
         | 
| @@ -56,6 +60,11 @@ module Extralite | |
| 56 60 | 
             
                  value.is_a?(Hash) ? pragma_set(value) : pragma_get(value)
         | 
| 57 61 | 
             
                end
         | 
| 58 62 |  | 
| 63 | 
            +
                # Error class used to roll back a transaction without propagating an
         | 
| 64 | 
            +
                # exception.
         | 
| 65 | 
            +
                class Rollback < Error
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 59 68 | 
             
                # Starts a transaction and runs the given block. If an exception is raised
         | 
| 60 69 | 
             
                # in the block, the transaction is rolled back. Otherwise, the transaction
         | 
| 61 70 | 
             
                # is commited after running the block.
         | 
| @@ -72,13 +81,56 @@ module Extralite | |
| 72 81 |  | 
| 73 82 | 
             
                  abort = false
         | 
| 74 83 | 
             
                  yield self
         | 
| 75 | 
            -
                rescue
         | 
| 84 | 
            +
                rescue => e
         | 
| 76 85 | 
             
                  abort = true
         | 
| 77 | 
            -
                  raise
         | 
| 86 | 
            +
                  raise unless e.is_a?(Rollback)
         | 
| 78 87 | 
             
                ensure
         | 
| 79 88 | 
             
                  execute(abort ? 'rollback' : 'commit')
         | 
| 80 89 | 
             
                end
         | 
| 81 90 |  | 
| 91 | 
            +
                # Creates a savepoint with the given name.
         | 
| 92 | 
            +
                #
         | 
| 93 | 
            +
                # @param name [String, Symbol] savepoint name
         | 
| 94 | 
            +
                # @return [Extralite::Database] database
         | 
| 95 | 
            +
                def savepoint(name)
         | 
| 96 | 
            +
                  execute "savepoint #{name}"
         | 
| 97 | 
            +
                  self
         | 
| 98 | 
            +
                end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                # Release a savepoint with the given name.
         | 
| 101 | 
            +
                #
         | 
| 102 | 
            +
                # @param name [String, Symbol] savepoint name
         | 
| 103 | 
            +
                # @return [Extralite::Database] database
         | 
| 104 | 
            +
                def release(name)
         | 
| 105 | 
            +
                  execute "release #{name}"
         | 
| 106 | 
            +
                  self
         | 
| 107 | 
            +
                end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                # Rolls back changes to a savepoint with the given name.
         | 
| 110 | 
            +
                #
         | 
| 111 | 
            +
                # @param name [String, Symbol] savepoint name
         | 
| 112 | 
            +
                # @return [Extralite::Database] database
         | 
| 113 | 
            +
                def rollback_to(name)
         | 
| 114 | 
            +
                  execute "rollback to #{name}"
         | 
| 115 | 
            +
                  self
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                # Rolls back the currently active transaction. This method should only be
         | 
| 119 | 
            +
                # called from within a block passed to Database#transaction. This method
         | 
| 120 | 
            +
                # raises a Extralite::Rollback exception, which will stop execution of the
         | 
| 121 | 
            +
                # transaction block without propagating the exception.
         | 
| 122 | 
            +
                #
         | 
| 123 | 
            +
                #   db.transaction do
         | 
| 124 | 
            +
                #     db.execute('insert into foo (42)')
         | 
| 125 | 
            +
                #     db.rollback!
         | 
| 126 | 
            +
                #   end
         | 
| 127 | 
            +
                #
         | 
| 128 | 
            +
                # @param name [String, Symbol] savepoint name
         | 
| 129 | 
            +
                # @return [Extralite::Database] database
         | 
| 130 | 
            +
                def rollback!
         | 
| 131 | 
            +
                  raise Rollback
         | 
| 132 | 
            +
                end
         | 
| 133 | 
            +
             | 
| 82 134 | 
             
                private
         | 
| 83 135 |  | 
| 84 136 | 
             
                def pragma_set(values)
         | 
| @@ -87,7 +139,11 @@ module Extralite | |
| 87 139 | 
             
                end
         | 
| 88 140 |  | 
| 89 141 | 
             
                def pragma_get(key)
         | 
| 90 | 
            -
                   | 
| 142 | 
            +
                  query_single_value("pragma #{key}")
         | 
| 91 143 | 
             
                end
         | 
| 92 144 | 
             
              end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
              class Query
         | 
| 147 | 
            +
                alias_method :execute_multi, :batch_execute
         | 
| 148 | 
            +
              end
         | 
| 93 149 | 
             
            end
         | 
    
        data/test/helper.rb
    CHANGED
    
    | @@ -7,3 +7,11 @@ require 'minitest/autorun' | |
| 7 7 | 
             
            puts "sqlite3 version: #{Extralite.sqlite3_version}"
         | 
| 8 8 |  | 
| 9 9 | 
             
            IS_LINUX = RUBY_PLATFORM =~ /linux/
         | 
| 10 | 
            +
            SKIP_RACTOR_TESTS = !IS_LINUX || (RUBY_VERSION =~ /^3\.[01]/)
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            module Minitest::Assertions
         | 
| 13 | 
            +
              def assert_in_range exp_range, act
         | 
| 14 | 
            +
                msg = message(msg) { "Expected #{mu_pp(act)} to be in range #{mu_pp(exp_range)}" }
         | 
| 15 | 
            +
                assert exp_range.include?(act), msg
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
    
        data/test/issue-54.rb
    ADDED
    
    | @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "./lib/extralite"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            puts 'Connecting to database...'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            connection_1 = Extralite::Database.new("test.sqlite3")
         | 
| 8 | 
            +
            puts "#{connection_1} connected"
         | 
| 9 | 
            +
            connection_2 = Extralite::Database.new("test.sqlite3")
         | 
| 10 | 
            +
            connection_2.busy_timeout = 0
         | 
| 11 | 
            +
            puts "#{connection_2} connected"
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            [connection_1, connection_2].each do |connection|
         | 
| 14 | 
            +
              puts "#{connection} beginning transaction..."
         | 
| 15 | 
            +
              connection.execute "begin immediate transaction"
         | 
| 16 | 
            +
            end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            [connection_1, connection_2].each do |connection|
         | 
| 19 | 
            +
              puts "#{connection} rolling back transaction..."
         | 
| 20 | 
            +
              connection.execute "rollback transaction"
         | 
| 21 | 
            +
            end
         | 
    
        data/test/issue-59.rb
    ADDED
    
    | @@ -0,0 +1,70 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "./lib/extralite"
         | 
| 4 | 
            +
            require "benchmark"
         | 
| 5 | 
            +
            require "tempfile"
         | 
| 6 | 
            +
            require "fileutils"
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            p sqlite_version: Extralite.sqlite3_version
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            N = (ENV['N'] || 1000).to_i
         | 
| 11 | 
            +
            p N: N
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            fn1 = '/tmp/db1'
         | 
| 14 | 
            +
            fn2 = '/tmp/db2'
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            FileUtils.rm(fn1) rescue nil
         | 
| 17 | 
            +
            FileUtils.rm(fn2) rescue nil
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            p fn1: fn1
         | 
| 20 | 
            +
            p fn2: fn2
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            db1 = Extralite::Database.new fn1
         | 
| 23 | 
            +
            db1.execute "pragma journal_mode = wal;"
         | 
| 24 | 
            +
            db1.transaction do
         | 
| 25 | 
            +
              db1.execute "create table t1 ( a integer primary key, b text );"
         | 
| 26 | 
            +
              values = N.times.map { |i| "#{i}-#{rand(1000)}" }
         | 
| 27 | 
            +
              db1.execute_multi "insert into t1 ( b ) values ( ? );", values
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              p count: db1.query_single_value("select count(*) from t1")
         | 
| 30 | 
            +
              p some_rows: db1.query("select * from t1 limit 5")
         | 
| 31 | 
            +
            end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            db2 = Extralite::Database.new fn2
         | 
| 34 | 
            +
            db2.execute "pragma journal_mode = wal;"
         | 
| 35 | 
            +
            db2.execute "attach '#{fn1}' as db1;"
         | 
| 36 | 
            +
            db2.execute "create table t2 ( a integer primary key, b text );"
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            p main_tables: db2.tables
         | 
| 39 | 
            +
            p db1_tables: db2.tables('db1')
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            overall = Benchmark.realtime do
         | 
| 42 | 
            +
              t1 = Thread.new do
         | 
| 43 | 
            +
                time1 = Benchmark.realtime do
         | 
| 44 | 
            +
                  db2.execute "create unique index db1.t1_b_unique on t1 (b);"
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
                p({ indexing: time1 })
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              t2 = Thread.new do
         | 
| 50 | 
            +
                time2 = Benchmark.realtime do
         | 
| 51 | 
            +
                  (N / 10000).times do |i|
         | 
| 52 | 
            +
                    values = 10000.times.map { |i| "#{i}-#{rand(1000)}" }
         | 
| 53 | 
            +
                    db2.transaction do
         | 
| 54 | 
            +
                      db2.execute_multi "insert into main.t2 ( b ) values ( ? );", values
         | 
| 55 | 
            +
                    end
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
                p({ inserting: time2 })
         | 
| 59 | 
            +
                p count_t2: db2.query_single_value("select count(*) from main.t2")
         | 
| 60 | 
            +
                p some_rows_t2: db2.query("select * from main.t2 limit 5")
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
              t1.join
         | 
| 64 | 
            +
              t2.join
         | 
| 65 | 
            +
            end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            p({ overall: overall })
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            db1.close
         | 
| 70 | 
            +
            db2.close
         | 
    
        data/test/perf_ary.rb
    CHANGED
    
    | @@ -15,26 +15,25 @@ require 'fileutils' | |
| 15 15 | 
             
            DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
         | 
| 16 16 | 
             
            puts "DB_PATH = #{DB_PATH.inspect}"
         | 
| 17 17 |  | 
| 18 | 
            +
            $sqlite3_db = SQLite3::Database.new(DB_PATH)
         | 
| 19 | 
            +
            $extralite_db = Extralite::Database.new(DB_PATH, gvl_release_threshold: -1)
         | 
| 18 20 |  | 
| 19 21 | 
             
            def prepare_database(count)
         | 
| 20 22 | 
             
              db = Extralite::Database.new(DB_PATH)
         | 
| 21 | 
            -
               | 
| 22 | 
            -
               | 
| 23 | 
            -
               | 
| 24 | 
            -
              count.times {  | 
| 25 | 
            -
               | 
| 26 | 
            -
              db.close
         | 
| 23 | 
            +
              $extralite_db.query('create table if not exists foo ( a integer primary key, b text )')
         | 
| 24 | 
            +
              $extralite_db.query('delete from foo')
         | 
| 25 | 
            +
              $extralite_db.query('begin')
         | 
| 26 | 
            +
              count.times { $extralite_db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
         | 
| 27 | 
            +
              $extralite_db.query('commit')
         | 
| 27 28 | 
             
            end
         | 
| 28 29 |  | 
| 29 30 | 
             
            def sqlite3_run(count)
         | 
| 30 | 
            -
               | 
| 31 | 
            -
              results = db.execute('select * from foo')
         | 
| 31 | 
            +
              results = $sqlite3_db.execute('select * from foo')
         | 
| 32 32 | 
             
              raise unless results.size == count
         | 
| 33 33 | 
             
            end
         | 
| 34 34 |  | 
| 35 35 | 
             
            def extralite_run(count)
         | 
| 36 | 
            -
               | 
| 37 | 
            -
              results = db.query_ary('select * from foo')
         | 
| 36 | 
            +
              results = $extralite_db.query('select * from foo')
         | 
| 38 37 | 
             
              raise unless results.size == count
         | 
| 39 38 | 
             
            end
         | 
| 40 39 |  | 
| @@ -43,12 +42,15 @@ end | |
| 43 42 |  | 
| 44 43 | 
             
              prepare_database(c)
         | 
| 45 44 |  | 
| 46 | 
            -
              Benchmark.ips do |x|
         | 
| 47 | 
            -
                x.config(:time =>  | 
| 45 | 
            +
              bm = Benchmark.ips do |x|
         | 
| 46 | 
            +
                x.config(:time => 5, :warmup => 2)
         | 
| 48 47 |  | 
| 49 48 | 
             
                x.report("sqlite3") { sqlite3_run(c) }
         | 
| 50 49 | 
             
                x.report("extralite") { extralite_run(c) }
         | 
| 51 50 |  | 
| 52 51 | 
             
                x.compare!
         | 
| 53 52 | 
             
              end
         | 
| 53 | 
            +
              puts;
         | 
| 54 | 
            +
              bm.entries.each { |e| puts "#{e.label}: #{(e.ips * c).round.to_i} rows/s" }
         | 
| 55 | 
            +
              puts;
         | 
| 54 56 | 
             
            end
         | 
    
        data/test/perf_hash.rb
    CHANGED
    
    | @@ -1,5 +1,7 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            +
            # Run on Ruby 3.3 with YJIT enabled
         | 
| 4 | 
            +
             | 
| 3 5 | 
             
            require 'bundler/inline'
         | 
| 4 6 |  | 
| 5 7 | 
             
            gemfile do
         | 
| @@ -15,40 +17,40 @@ require 'fileutils' | |
| 15 17 | 
             
            DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
         | 
| 16 18 | 
             
            puts "DB_PATH = #{DB_PATH.inspect}"
         | 
| 17 19 |  | 
| 20 | 
            +
            $sqlite3_db = SQLite3::Database.new(DB_PATH, results_as_hash: true)
         | 
| 21 | 
            +
            $extralite_db = Extralite::Database.new(DB_PATH, gvl_release_threshold: -1)
         | 
| 22 | 
            +
             | 
| 18 23 | 
             
            def prepare_database(count)
         | 
| 19 | 
            -
               | 
| 20 | 
            -
               | 
| 21 | 
            -
               | 
| 22 | 
            -
               | 
| 23 | 
            -
               | 
| 24 | 
            -
              db.query('commit')
         | 
| 25 | 
            -
              db.close
         | 
| 24 | 
            +
              $extralite_db.query('create table if not exists foo ( a integer primary key, b text )')
         | 
| 25 | 
            +
              $extralite_db.query('delete from foo')
         | 
| 26 | 
            +
              $extralite_db.query('begin')
         | 
| 27 | 
            +
              count.times { $extralite_db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
         | 
| 28 | 
            +
              $extralite_db.query('commit')
         | 
| 26 29 | 
             
            end
         | 
| 27 30 |  | 
| 28 31 | 
             
            def sqlite3_run(count)
         | 
| 29 | 
            -
               | 
| 30 | 
            -
              results = db.execute('select * from foo')
         | 
| 32 | 
            +
              results = $sqlite3_db.execute('select * from foo')
         | 
| 31 33 | 
             
              raise unless results.size == count
         | 
| 32 34 | 
             
            end
         | 
| 33 35 |  | 
| 34 36 | 
             
            def extralite_run(count)
         | 
| 35 | 
            -
               | 
| 36 | 
            -
              results = db.query('select * from foo')
         | 
| 37 | 
            +
              results = $extralite_db.query('select * from foo')
         | 
| 37 38 | 
             
              raise unless results.size == count
         | 
| 38 39 | 
             
            end
         | 
| 39 40 |  | 
| 40 41 | 
             
            [10, 1000, 100000].each do |c|
         | 
| 41 42 | 
             
              puts "Record count: #{c}"
         | 
| 42 | 
            -
             | 
| 43 43 | 
             
              prepare_database(c)
         | 
| 44 44 |  | 
| 45 | 
            -
              Benchmark.ips do |x|
         | 
| 46 | 
            -
                x.config(:time =>  | 
| 45 | 
            +
              bm = Benchmark.ips do |x|
         | 
| 46 | 
            +
                x.config(:time => 5, :warmup => 2)
         | 
| 47 47 |  | 
| 48 48 | 
             
                x.report("sqlite3") { sqlite3_run(c) }
         | 
| 49 49 | 
             
                x.report("extralite") { extralite_run(c) }
         | 
| 50 50 |  | 
| 51 51 | 
             
                x.compare!
         | 
| 52 52 | 
             
              end
         | 
| 53 | 
            -
              puts; | 
| 53 | 
            +
              puts;
         | 
| 54 | 
            +
              bm.entries.each { |e| puts "#{e.label}: #{(e.ips * c).round.to_i} rows/s" }
         | 
| 55 | 
            +
              puts;
         | 
| 54 56 | 
             
            end
         | 
| @@ -0,0 +1,58 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'bundler/inline'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            gemfile do
         | 
| 6 | 
            +
              source 'https://rubygems.org'
         | 
| 7 | 
            +
              gem 'extralite', path: '..'
         | 
| 8 | 
            +
              gem 'sqlite3'
         | 
| 9 | 
            +
              gem 'benchmark-ips'
         | 
| 10 | 
            +
            end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            require 'benchmark/ips'
         | 
| 13 | 
            +
            require 'fileutils'
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
         | 
| 16 | 
            +
            puts "DB_PATH = #{DB_PATH.inspect}"
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            def prepare_database(count)
         | 
| 19 | 
            +
              $sqlite3_db = SQLite3::Database.new(DB_PATH, results_as_hash: true)
         | 
| 20 | 
            +
              $extralite_db = Extralite::Database.new(DB_PATH, gvl_release_threshold: -1)
         | 
| 21 | 
            +
                
         | 
| 22 | 
            +
              $extralite_db.query('create table if not exists foo ( a integer primary key, b text )')
         | 
| 23 | 
            +
              $extralite_db.query('delete from foo')
         | 
| 24 | 
            +
              $extralite_db.query('begin')
         | 
| 25 | 
            +
              count.times { $extralite_db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
         | 
| 26 | 
            +
              $extralite_db.query('commit')
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              $sqlite3_stmt = $sqlite3_db.prepare('select * from foo')
         | 
| 29 | 
            +
              $extralite_q = $extralite_db.prepare('select * from foo')
         | 
| 30 | 
            +
            end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            def sqlite3_run(count)
         | 
| 33 | 
            +
              results = $sqlite3_stmt.execute
         | 
| 34 | 
            +
              raise unless results.to_a.size == count
         | 
| 35 | 
            +
            end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            def extralite_run(count)
         | 
| 38 | 
            +
              results = $extralite_q.to_a
         | 
| 39 | 
            +
              raise unless results.size == count
         | 
| 40 | 
            +
            end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            [10, 1000, 100000].each do |c|
         | 
| 43 | 
            +
              puts "Record count: #{c}"
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              prepare_database(c)
         | 
| 46 | 
            +
             | 
| 47 | 
            +
              bm = Benchmark.ips do |x|
         | 
| 48 | 
            +
                x.config(:time => 5, :warmup => 2)
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                x.report("sqlite3") { sqlite3_run(c) }
         | 
| 51 | 
            +
                x.report("extralite") { extralite_run(c) }
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                x.compare!
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
              puts;
         | 
| 56 | 
            +
              bm.entries.each { |e| puts "#{e.label}: #{(e.ips * c).round.to_i} rows/s" }
         | 
| 57 | 
            +
              puts;
         | 
| 58 | 
            +
            end
         |