amalgalite 1.8.0-x64-mingw-ucrt

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTING.md +60 -0
  3. data/HISTORY.md +386 -0
  4. data/LICENSE +31 -0
  5. data/Manifest.txt +105 -0
  6. data/README.md +62 -0
  7. data/Rakefile +27 -0
  8. data/TODO.md +57 -0
  9. data/bin/amalgalite-pack +147 -0
  10. data/examples/a.rb +9 -0
  11. data/examples/blob.rb +88 -0
  12. data/examples/bootstrap.rb +36 -0
  13. data/examples/define_aggregate.rb +75 -0
  14. data/examples/define_function.rb +104 -0
  15. data/examples/fts5.rb +152 -0
  16. data/examples/gem-db.rb +94 -0
  17. data/examples/require_me.rb +11 -0
  18. data/examples/requires.rb +42 -0
  19. data/examples/schema-info.rb +34 -0
  20. data/ext/amalgalite/c/amalgalite.c +355 -0
  21. data/ext/amalgalite/c/amalgalite.h +151 -0
  22. data/ext/amalgalite/c/amalgalite_blob.c +240 -0
  23. data/ext/amalgalite/c/amalgalite_constants.c +1432 -0
  24. data/ext/amalgalite/c/amalgalite_database.c +1188 -0
  25. data/ext/amalgalite/c/amalgalite_requires_bootstrap.c +282 -0
  26. data/ext/amalgalite/c/amalgalite_statement.c +649 -0
  27. data/ext/amalgalite/c/extconf.rb +71 -0
  28. data/ext/amalgalite/c/gen_constants.rb +353 -0
  29. data/ext/amalgalite/c/notes.txt +134 -0
  30. data/ext/amalgalite/c/sqlite3.c +243616 -0
  31. data/ext/amalgalite/c/sqlite3.h +12894 -0
  32. data/ext/amalgalite/c/sqlite3_options.h +4 -0
  33. data/ext/amalgalite/c/sqlite3ext.h +705 -0
  34. data/lib/amalgalite/3.1/amalgalite.so +0 -0
  35. data/lib/amalgalite/aggregate.rb +73 -0
  36. data/lib/amalgalite/blob.rb +186 -0
  37. data/lib/amalgalite/boolean.rb +42 -0
  38. data/lib/amalgalite/busy_timeout.rb +47 -0
  39. data/lib/amalgalite/column.rb +99 -0
  40. data/lib/amalgalite/core_ext/kernel/require.rb +21 -0
  41. data/lib/amalgalite/csv_table_importer.rb +75 -0
  42. data/lib/amalgalite/database.rb +933 -0
  43. data/lib/amalgalite/function.rb +61 -0
  44. data/lib/amalgalite/index.rb +43 -0
  45. data/lib/amalgalite/memory_database.rb +15 -0
  46. data/lib/amalgalite/packer.rb +231 -0
  47. data/lib/amalgalite/paths.rb +80 -0
  48. data/lib/amalgalite/profile_tap.rb +131 -0
  49. data/lib/amalgalite/progress_handler.rb +21 -0
  50. data/lib/amalgalite/requires.rb +151 -0
  51. data/lib/amalgalite/schema.rb +225 -0
  52. data/lib/amalgalite/sqlite3/constants.rb +95 -0
  53. data/lib/amalgalite/sqlite3/database/function.rb +48 -0
  54. data/lib/amalgalite/sqlite3/database/status.rb +68 -0
  55. data/lib/amalgalite/sqlite3/status.rb +60 -0
  56. data/lib/amalgalite/sqlite3/version.rb +55 -0
  57. data/lib/amalgalite/sqlite3.rb +6 -0
  58. data/lib/amalgalite/statement.rb +421 -0
  59. data/lib/amalgalite/table.rb +91 -0
  60. data/lib/amalgalite/taps/console.rb +27 -0
  61. data/lib/amalgalite/taps/io.rb +74 -0
  62. data/lib/amalgalite/taps.rb +2 -0
  63. data/lib/amalgalite/trace_tap.rb +35 -0
  64. data/lib/amalgalite/type_map.rb +63 -0
  65. data/lib/amalgalite/type_maps/default_map.rb +166 -0
  66. data/lib/amalgalite/type_maps/storage_map.rb +38 -0
  67. data/lib/amalgalite/type_maps/text_map.rb +21 -0
  68. data/lib/amalgalite/version.rb +8 -0
  69. data/lib/amalgalite/view.rb +26 -0
  70. data/lib/amalgalite.rb +51 -0
  71. data/spec/aggregate_spec.rb +158 -0
  72. data/spec/amalgalite_spec.rb +4 -0
  73. data/spec/blob_spec.rb +78 -0
  74. data/spec/boolean_spec.rb +24 -0
  75. data/spec/busy_handler.rb +157 -0
  76. data/spec/data/iso-3166-country.txt +242 -0
  77. data/spec/data/iso-3166-schema.sql +22 -0
  78. data/spec/data/iso-3166-subcountry.txt +3995 -0
  79. data/spec/data/make-iso-db.sh +12 -0
  80. data/spec/database_spec.rb +505 -0
  81. data/spec/default_map_spec.rb +92 -0
  82. data/spec/function_spec.rb +78 -0
  83. data/spec/integeration_spec.rb +97 -0
  84. data/spec/iso_3166_database.rb +58 -0
  85. data/spec/json_spec.rb +24 -0
  86. data/spec/packer_spec.rb +60 -0
  87. data/spec/paths_spec.rb +28 -0
  88. data/spec/progress_handler_spec.rb +91 -0
  89. data/spec/requires_spec.rb +54 -0
  90. data/spec/rtree_spec.rb +66 -0
  91. data/spec/schema_spec.rb +131 -0
  92. data/spec/spec_helper.rb +48 -0
  93. data/spec/sqlite3/constants_spec.rb +108 -0
  94. data/spec/sqlite3/database_status_spec.rb +36 -0
  95. data/spec/sqlite3/status_spec.rb +22 -0
  96. data/spec/sqlite3/version_spec.rb +28 -0
  97. data/spec/sqlite3_spec.rb +53 -0
  98. data/spec/statement_spec.rb +168 -0
  99. data/spec/storage_map_spec.rb +38 -0
  100. data/spec/tap_spec.rb +57 -0
  101. data/spec/text_map_spec.rb +20 -0
  102. data/spec/type_map_spec.rb +14 -0
  103. data/spec/version_spec.rb +8 -0
  104. data/tasks/custom.rake +101 -0
  105. data/tasks/default.rake +244 -0
  106. data/tasks/extension.rake +28 -0
  107. data/tasks/this.rb +208 -0
  108. metadata +325 -0
data/TODO.md ADDED
@@ -0,0 +1,57 @@
1
+ # Future Release possibilties:
2
+ - rebuild statement constants
3
+ - look at all pragma statements
4
+
5
+ ## SQLite API:
6
+ - authorizers
7
+ - loading of extensions -- readfile / writefile
8
+ - utf-16 integration
9
+ - create_collation
10
+ - encryption key support
11
+ - expose sqlite3_strnicmp
12
+ - table name and column name in a type map?
13
+ - type conversion for manifest typing? how to allow it through?
14
+ - explicit pragma handler
15
+ - application_id pragma setter
16
+
17
+ ## Non backwards compatible changes:
18
+ - change the schema objects to be more consistent
19
+ - change taps to use to_proc protocol
20
+ - convert type dependency to just use 'call'
21
+ - integrate transaction and savepoint under the same api
22
+
23
+ ## SQLite Features:
24
+ - activate SQLITE_ENABLE_ICU extension
25
+ - activate SQLITE_ENABLE_LOCKING_STYLE
26
+ - activate SQLITE_ENABLE_UNLOCK_NOTIFY
27
+ - expose PRAGMA foreign_keys
28
+ - virtual file system
29
+ - full text search (FTS3)
30
+ - expose the sqlite mutex lib
31
+ - statement status ( sqlite3_stmt_status )
32
+ - db status ( sqlite3_db_status )
33
+ - library status ( sqlite3_status )
34
+ - sqlite3_index_info
35
+ - sqlite3_create_function has 4th parameter SQLITE_DETERMINISTIC
36
+ - sqlite3_rtree_query_callback()
37
+
38
+ ## Drivers:
39
+ - data mapper driver
40
+ - sequel driver optimization
41
+
42
+ ## Features:
43
+ - Think about moving from arrayfields to ordered hash?
44
+ - add to command line which directory to pack into a rubylibs table
45
+ - amalgalite command line tool
46
+ - use ruby's ALLOC_N and hook into sqlite3_mem_methods
47
+
48
+ ## Functions to possibly expose:
49
+ - sqlite3_backup_remaining, sqlite3_backup_pagecount
50
+ - sqlite3_compileoption_used, sqlite3_compileoption_get
51
+ - sqlite3_config
52
+ - sqlite3_data_count - returns number of colums in the result set of a
53
+ prepared statement
54
+ - sqlite_sourceid, sqlite_source_id
55
+ - sqlite3_strnicmp
56
+ -
57
+
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'pathname'
4
+
5
+ #
6
+ # add relative paths to the load path if we are not a gem and calculate what the
7
+ # strip path will be if we decide to pack --self
8
+ #
9
+ this_path = Pathname.new( File.expand_path( __FILE__ ) )
10
+ gem_path = Pathname.new( Gem.dir )
11
+ rel_path = this_path.relative_path_from( gem_path )
12
+ if ".." == rel_path.to_s.split( File::SEPARATOR ).first then
13
+ lib_path = File.join( File.dirname( __FILE__ ), "../lib" )
14
+ $:.unshift lib_path
15
+ $:.unshift File.join( File.dirname( __FILE__ ), "../ext" )
16
+ end
17
+
18
+ #
19
+ # snapshot of what is needed for amalgalite requires, this info may only be used
20
+ # when packing amalgalite itself
21
+ #
22
+ loaded_features_before = $LOADED_FEATURES.dup
23
+ require 'amalgalite/packer'
24
+ loaded_features_after = $LOADED_FEATURES.dup
25
+ load_diff = loaded_features_after - loaded_features_before
26
+
27
+ #
28
+ # strip off any LOAD_PATH elements from the front of load_diff since that
29
+ # will conflict with Amalgalite::Packer.amalgalite_require_order. Also
30
+ # strip out any 'rubygems' items since those are not used by Amalgalite
31
+ # and show as a side effect fo the "require 'amalgalite/packer'"
32
+ #
33
+ strip_paths = $LOAD_PATH.sort.reverse
34
+ amalgalite_needs = []
35
+ load_diff.each do |f|
36
+ next if f.split( File::SEPARATOR ).include?( "rubygems" )
37
+ appended = false
38
+ strip_paths.each do |path|
39
+ if 0 == f.index(path ) then
40
+ rel_path = f.sub( path, '' ).sub(%r{\A#{File::SEPARATOR}},'')
41
+ amalgalite_needs << rel_path
42
+ appended = true
43
+ break
44
+ end
45
+ end
46
+ amalgalite_needs << f unless appended
47
+ end
48
+
49
+ #
50
+ # Commandline parser
51
+ #
52
+ options = {}
53
+ begin
54
+ parser = OptionParser.new do |op|
55
+ op.banner = "Usage: #{op.program_name} [options] <files>"
56
+ op.separator ""
57
+
58
+ op.on("--dbfile DB", "The Database file in which to pack files") do |d|
59
+ options[:dbfile] = d
60
+ end
61
+
62
+ op.on("--drop-table", "Drop the table before inserting rows") do |t|
63
+ options[:drop_table] = t
64
+ end
65
+
66
+ op.on("-m", "--merge", "Merge these files into the existing table overwriting rows that conflict") do |m|
67
+ options[:merge] = true
68
+ end
69
+
70
+ op.on("--require-order", "Dump Amalgalite's require order" ) do |m|
71
+ puts amalgalite_needs
72
+ exit 0
73
+ end
74
+
75
+ op.on("--self", "pack amalgalite itself into the database") do |d|
76
+ options[:self] = true
77
+ end
78
+
79
+ op.on("--strip-prefix PREFIX", "strip this path prefix off the front of each file") do |p|
80
+ options[:strip_prefix] = File.expand_path( p )
81
+ end
82
+
83
+ op.on("-t", "--table TABLE", "the table name to pack into") do |t|
84
+ options[:table_name] = t
85
+ end
86
+
87
+ op.on("--verbose", "Be verbose about output") do |v|
88
+ options[:verbose] = v
89
+ end
90
+
91
+ op.on("-z", "--compressed", "compress the file contents on storage") do |z|
92
+ options[:compressed] = true
93
+ end
94
+
95
+ end
96
+
97
+ parser.parse!
98
+ require 'amalgalite/packer'
99
+ file_list = ARGV.dup
100
+
101
+
102
+ if options[:self] then
103
+ options[:table_name] = Amalgalite::Requires::Bootstrap::DEFAULT_BOOTSTRAP_TABLE
104
+ core_libs = (amalgalite_needs - Amalgalite::Packer.amalgalite_require_order).delete_if { |l| l.index(".rb").nil? }
105
+
106
+ #
107
+ # check and make sure nothing is missed
108
+ #
109
+ core_libs.each do |l|
110
+ if l.index("amalgalite") then
111
+ STDERR.puts "ERROR! require_order needs an update #{l}"
112
+ exit 2
113
+ end
114
+ end
115
+ file_list = core_libs.concat( Amalgalite::Packer.amalgalite_require_order )
116
+ if options[:compressed] then
117
+ STDERR.puts "Compressing --self is not allowed, reverting to uncompressed"
118
+ options[:compressed] = false
119
+ end
120
+ end
121
+ STDERR.puts parser if file_list.empty?
122
+
123
+ packer = Amalgalite::Packer.new( options )
124
+ packer.pack( file_list )
125
+
126
+ rescue OptionParser::ParseError => pe
127
+ STDERR.puts "ERROR : #{pe}"
128
+ STDERR.puts parser
129
+ exit 1
130
+ end
131
+
132
+ __END__
133
+
134
+ puts <<-text
135
+
136
+ Packing complete. To utilize the bootstrapping in #{dbfile} you must do
137
+ one of the following:
138
+
139
+ * statically compile the amalgalite C extension into your application
140
+ * require 'amalgalite/#{RUBY_VERSION.sub(/\.\d$/,'')}/amalgalite3'
141
+
142
+ Once one of those is working you can boostrap the Amalgalite library with
143
+ this line in your code:
144
+
145
+ Amalgalite::Requries::Boostrap.lift( 'dbfile' => '#{dbfile}' )
146
+
147
+ text
data/examples/a.rb ADDED
@@ -0,0 +1,9 @@
1
+ class A
2
+ def initialize
3
+ puts "Initialized A"
4
+ end
5
+
6
+ def a
7
+ puts "called a"
8
+ end
9
+ end
data/examples/blob.rb ADDED
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #
4
+ # An Amalgalite example showing how Blob's can be utilized
5
+ #
6
+ # We'll make a database with one table, that we store files in. We'll use the
7
+ # Blob incremental IO to store the files and retrieve them from the database
8
+ #
9
+ # This little program will store 1 or more files in the sqlite3 database when
10
+ # the 'store' action is given, and cat a file to stdout on 'retrieve'
11
+ #
12
+ # e.g.
13
+ #
14
+ # ruby blob.rb store a.rb b.rb c.rb # => stores a.rb b.rb and c.rb in the db
15
+ #
16
+ # ruby blob.rb retrieve a.rb # => dumps a.rb to stdout
17
+ #
18
+
19
+ require 'rubygems'
20
+ $: << "../lib"
21
+ $: << "../ext"
22
+ require 'amalgalite'
23
+ require 'amalgalite/packer'
24
+ VALID_ACTIONS = %w[ list retrieve store ]
25
+ def usage
26
+ STDERR.puts "Usage: #{File.basename($0)} ( #{VALID_ACTIONS.join(' | ')} ) file(s)"
27
+ exit 1
28
+ end
29
+
30
+ #
31
+ # This does the basic command line parsing
32
+ #
33
+ usage if ARGV.size < 1
34
+ action = ARGV.shift
35
+ usage unless VALID_ACTIONS.include? action
36
+ file_list = ARGV
37
+
38
+ #
39
+ # create the database if it doesn't exist
40
+ #
41
+ db = Amalgalite::Database.new( "filestore.db" )
42
+
43
+ case action
44
+ #
45
+ # list all the files that are stored in the database
46
+ #
47
+ when 'list'
48
+ db.execute("SELECT filename FROM rubylibs") do |row|
49
+ puts row['filename']
50
+ end
51
+
52
+ #
53
+ # if we are doing the store action, then loop over the files and store them in
54
+ # the database. This will use incremental IO to store the files directly from
55
+ # the file names.
56
+ #
57
+ # It is slightly strange in that you have to tell the Blob object what column
58
+ # it is going to, but that is necessary at this point to be able to hook
59
+ # automatically into the lower level incremental blob IO api.
60
+ #
61
+ # This also shows using the $var syntax for binding name sql values in a
62
+ # prepared statement.
63
+ #
64
+ when 'store'
65
+ usage if file_list.empty?
66
+ require 'amalgalite/packer'
67
+
68
+ packer = Amalgalite::Packer.new( :dbfile => 'filestore.db',
69
+ :compressed => false )
70
+ packer.pack( file_list )
71
+
72
+ #
73
+ # dump the file that matches the right path to stdout. This also shows
74
+ # positional sql varible binding using the '?' syntax.
75
+ #
76
+ when 'retrieve'
77
+ usage if file_list.empty?
78
+ db.execute("SELECT id, compressed, filename, contents FROM rubylibs WHERE filename = ?", file_list.first) do |row|
79
+ STDERR.puts "Dumping #{row['filename']} to stdout"
80
+ if row['compressed'] then
81
+ s = row['contents'].to_s
82
+ STDOUT.puts Amalgalite::Packer.gunzip( s )
83
+ else
84
+ row['contents'].write_to_io( STDOUT )
85
+ end
86
+ end
87
+ end
88
+ db.close
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # An example of requiring all the files in a table via the Bootstrap::lift
4
+ # method.
5
+ #
6
+ # First use the blob.rb example in this same directory to load the a.rb file
7
+ # into an example database:
8
+ #
9
+ # ruby blob.rb store a.rb
10
+ #
11
+ # Then run this example.
12
+ #
13
+
14
+ # Require "ONLY" then binary extension, do not +require+ the ruby based code
15
+ $: << "../ext"
16
+ require 'amalgalite/amalgalite3'
17
+
18
+ # See what the load path is, notice that it is very small
19
+ puts "Before $\" : #{$LOADED_FEATURES.inspect}"
20
+
21
+ # tell the binary extension to "require" every file in the filestore.db in the
22
+ # table 'files' orderd by column 'id'. The 'path' column is added to $LOADED_FEATURES and the
23
+ # code in 'data' is evaled.
24
+ Amalgalite::Requires::Bootstrap.lift( "dbfile" => "filestore.db",
25
+ "table_name" => "rubylibs",
26
+ "rowid_column" => "id",
27
+ "filename_column" => "filename",
28
+ "contents_column" => "contents" )
29
+
30
+ # Notice that a.rb is in the list of files that has been required
31
+ puts "After $\" : #{$LOADED_FEATURES.inspect}"
32
+
33
+ # and look we prove that the code was eval'd appropriately
34
+ a = A.new
35
+ a.a
36
+
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ $: << "../lib"
5
+ $: << "../ext"
6
+ require 'amalgalite'
7
+
8
+ #--
9
+ # Create a database and a table to put some results from the functions in
10
+ #--
11
+ db = Amalgalite::Database.new( ":memory:" )
12
+ db.execute( "CREATE TABLE atest( words )" )
13
+
14
+ #------------------------------------------------------------------------------
15
+ # Create unique word count aggregate
16
+ #------------------------------------------------------------------------------
17
+ class UniqueWordCount < ::Amalgalite::Aggregate
18
+ attr_accessor :words
19
+
20
+ def initialize
21
+ @name = 'unique_word_count'
22
+ @arity = 1
23
+ @words = Hash.new { |h,k| h[k] = 0 }
24
+ end
25
+
26
+ def step( str )
27
+ str.split(/\W+/).each do |word|
28
+ words[ word.downcase ] += 1
29
+ end
30
+ return nil
31
+ end
32
+
33
+ def finalize
34
+ return words.size
35
+ end
36
+ end
37
+
38
+ db.define_aggregate( 'unique_word_count', UniqueWordCount )
39
+
40
+ #------------------------------------------------------------------------------
41
+ # Now we have a new aggregate function, lets insert some rows into the database
42
+ # and see what we can find.
43
+ #------------------------------------------------------------------------------
44
+ sql = "INSERT INTO atest( words ) VALUES( ? )"
45
+ verify = {}
46
+ db.prepare( sql ) do |stmt|
47
+ DATA.each do |words|
48
+ words.strip!
49
+ puts "Inserting #{words}"
50
+ stmt.execute( words )
51
+ words.split(/\W+/).each { |w| verify[w] = true }
52
+ end
53
+ end
54
+
55
+ #------------------------------------------------------------------------------
56
+ # And show the results
57
+ #------------------------------------------------------------------------------
58
+ puts
59
+ puts "Getting results..."
60
+ puts
61
+ all_rows = db.execute("SELECT unique_word_count( words ) AS uwc FROM atest")
62
+ puts "#{all_rows.first['uwc']} unique words found"
63
+ puts "#{verify.size} unique words to verify"
64
+
65
+ __END__
66
+ some random
67
+ words with
68
+ which
69
+ to play
70
+ and there should
71
+ be a couple of different
72
+ words that appear
73
+ more than once and
74
+ some that appear only
75
+ once
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ $: << "../lib"
5
+ $: << "../ext"
6
+ require 'amalgalite'
7
+
8
+ #--
9
+ # Create a database and a table to put some results from the functions in
10
+ #--
11
+ db = Amalgalite::Database.new( ":memory:" )
12
+ db.execute( "CREATE TABLE ftest( data, md5, sha1, sha2_bits, sha2)" )
13
+
14
+ #------------------------------------------------------------------------------
15
+ # Create an MD5 method using the block format of defining an sql fuction
16
+ #------------------------------------------------------------------------------
17
+ require 'digest/md5'
18
+ db.define_function( 'md5' ) do |x|
19
+ Digest::MD5.hexdigest( x.to_s )
20
+ end
21
+
22
+ #------------------------------------------------------------------------------
23
+ # Create a SHA1 method using the lambda format of defining an sql function
24
+ #------------------------------------------------------------------------------
25
+ require 'digest/sha1'
26
+ sha1 = lambda do |y|
27
+ Digest::SHA1.hexdigest( y.to_s )
28
+ end
29
+ db.define_function( "sha1", sha1 )
30
+
31
+ #------------------------------------------------------------------------------
32
+ # Create a SHA2 method using the class format for defining an sql function
33
+ # In this one we will allow any number of parameters, but we will only use the
34
+ # first two.
35
+ #------------------------------------------------------------------------------
36
+ require 'digest/sha2'
37
+ class SQLSha2
38
+ # track the number of invocations
39
+ attr_reader :call_count
40
+
41
+ def initialize
42
+ @call_count = 0
43
+ end
44
+
45
+ # the protocol that is used for sql function definition
46
+ def to_proc() self ; end
47
+
48
+ # say we take any number of parameters
49
+ def arity
50
+ -1
51
+ end
52
+
53
+ # The method that is called by SQLite, must be named 'call'
54
+ def call( *args )
55
+ text = args.shift.to_s
56
+ bitlength = (args.shift || 256).to_i
57
+ Digest::SHA2.new( bitlength ).hexdigest( text )
58
+ end
59
+ end
60
+ db.define_function('sha2', SQLSha2.new)
61
+
62
+
63
+ #------------------------------------------------------------------------------
64
+ # Now we have 3 new sql functions, each defined in one of the available methods
65
+ # to define sql functions in amalgalite. Lets insert some rows and look at the
66
+ # results
67
+ #------------------------------------------------------------------------------
68
+ possible_bits = [ 256, 384, 512 ]
69
+ sql = "INSERT INTO ftest( data, md5, sha1, sha2_bits, sha2 ) VALUES( @word , md5( @word ), sha1( @word ), @bits, sha2(@word,@bits) )"
70
+ db.prepare( sql ) do |stmt|
71
+ DATA.each do |word|
72
+ word.strip!
73
+ bits = possible_bits[ rand(3) ]
74
+ puts "Inserting #{word}, #{bits}"
75
+ stmt.execute( { '@word' => word, '@bits' => bits } )
76
+ end
77
+ end
78
+
79
+ #------------------------------------------------------------------------------
80
+ # And show the results
81
+ #------------------------------------------------------------------------------
82
+ puts
83
+ puts "Getting results..."
84
+ puts
85
+ columns = db.schema.tables['ftest'].columns.keys.sort
86
+ i = 0
87
+ db.execute("SELECT #{columns.join(',')} FROM ftest") do |row|
88
+ i += 1
89
+ puts "-----[ row #{i} ]-" + "-" * 42
90
+ columns.each do |col|
91
+ puts "#{col.ljust(10)} : #{row[col]}"
92
+ end
93
+ puts
94
+ end
95
+
96
+
97
+ __END__
98
+ some
99
+ random
100
+ words
101
+ with
102
+ which
103
+ to
104
+ play
data/examples/fts5.rb ADDED
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'amalgalite'
4
+ require 'benchmark'
5
+ require 'pathname'
6
+
7
+ begin
8
+ require 'json'
9
+ rescue LoadError
10
+ abort "'gem install json' to run this example"
11
+ end
12
+
13
+
14
+ README = <<_
15
+ This example program assumes that you have available the 'fortune' BSD command dataset.
16
+
17
+ execute `fortune -f` to see if you have this command available.
18
+ _
19
+
20
+ fortune_dir = %x[ fortune -f 2>&1 ].split("\n").first.split.last
21
+ abort README unless fortune_dir and File.directory?( fortune_dir )
22
+
23
+ fortune_path = Pathname.new(fortune_dir)
24
+
25
+ #
26
+ # Lets create a database that utilizes FTS5 http://www.sqlite.org/fts5.html
27
+ #
28
+ #
29
+
30
+ #
31
+ # Create a database, this will create the DB if it doesn't exist
32
+ #
33
+ puts "Opening database (version #{Amalgalite::VERSION})"
34
+ db = Amalgalite::Database.new("fts5.db")
35
+
36
+ #
37
+ # Create the schema unless it already exists in the table. The meta information
38
+ # about the database schema is available as the result of the db.schema method
39
+ #
40
+ schema = db.schema
41
+ unless schema.tables['search']
42
+ puts "Create schema"
43
+ db.execute_batch <<-SQL
44
+ CREATE VIRTUAL TABLE search USING fts5(
45
+ filename,
46
+ content
47
+ );
48
+
49
+ CREATE TABLE plain (
50
+ filename VARCHAR(128),
51
+ content TEXT
52
+ );
53
+ SQL
54
+ db.reload_schema!
55
+ end
56
+
57
+ def each_fortune(path,&block)
58
+ fortune = []
59
+ path.each_line do |line|
60
+ line.strip!
61
+ if line == "%" then
62
+ yield fortune.join("\n")
63
+ fortune.clear
64
+ else
65
+ fortune << line
66
+ end
67
+ end
68
+ end
69
+
70
+ #
71
+ # Only load the data if the db is empty
72
+ #
73
+ if db.first_value_from( "SELECT count(*) from search" ) == 0 then
74
+ before = Time.now
75
+ idx = 0
76
+
77
+ # Inserting bulk rows as a transaction is good practice with SQLite, it is
78
+ # MUCH faster.
79
+ db.transaction do |db_in_transaction|
80
+ # Iterate over the files in the fortunes dir and split on the fortunes, then
81
+
82
+ fortune_path.each_child do |fortune_file|
83
+ next if fortune_file.directory?
84
+ next if fortune_file.extname == ".dat"
85
+ $stdout.puts "Loading #{fortune_file}"
86
+
87
+ each_fortune(fortune_file) do |fortune|
88
+ insert_data = {
89
+ ':fname' => fortune_file.to_s,
90
+ ':content' => fortune
91
+ }
92
+
93
+ # insert into the FTS5 table
94
+ db_in_transaction.prepare("INSERT INTO search( filename, content ) VALUES( :fname, :content );") do |stmt|
95
+ stmt.execute( insert_data )
96
+ end
97
+
98
+ # insert into the normal table for comparison
99
+ db_in_transaction.prepare("INSERT INTO plain( filename, content ) VALUES( :fname, :content );") do |stmt|
100
+ stmt.execute( insert_data )
101
+ end
102
+
103
+ idx += 1
104
+ print "Processed #{idx}\r"
105
+ $stdout.flush
106
+ end
107
+ puts "\nFinalizing..."
108
+ end
109
+ end
110
+ puts "Took #{Time.now - before} seconds to insert #{idx} fortunes"
111
+ puts "Done Inserting"
112
+ end
113
+
114
+ doc_count = db.first_value_from( "SELECT count(*) from search" )
115
+
116
+ #
117
+ # Now lets do some searching for some various words
118
+ #
119
+
120
+ %w[ president salmon thanks ].each do |word|
121
+
122
+ count = 100
123
+ puts
124
+ puts "Searching for '#{word}' #{count} times across #{doc_count} fortunes"
125
+ puts "=" * 60
126
+
127
+ Benchmark.bm( 15 ) do |x|
128
+
129
+ #
130
+ # search using the fts search to get the cont of tweets with the given word
131
+ #
132
+ x.report('fts5: ') do
133
+ db.prepare( "SELECT count(filename) FROM search WHERE search MATCH 'content:#{word}'" ) do |stmt|
134
+ count.times do
135
+ stmt.execute
136
+ end
137
+ end
138
+ end
139
+
140
+ #
141
+ # search using basic string matching in sqlite.
142
+ #
143
+ x.report('plain: ') do
144
+ db.prepare( "SELECT count(filename) FROM plain WHERE content LIKE '% #{word} %'" ) do |stmt|
145
+ count.times do
146
+ stmt.execute
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
152
+