amalgalite 1.8.0-x64-mingw-ucrt
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CONTRIBUTING.md +60 -0
- data/HISTORY.md +386 -0
- data/LICENSE +31 -0
- data/Manifest.txt +105 -0
- data/README.md +62 -0
- data/Rakefile +27 -0
- data/TODO.md +57 -0
- data/bin/amalgalite-pack +147 -0
- data/examples/a.rb +9 -0
- data/examples/blob.rb +88 -0
- data/examples/bootstrap.rb +36 -0
- data/examples/define_aggregate.rb +75 -0
- data/examples/define_function.rb +104 -0
- data/examples/fts5.rb +152 -0
- data/examples/gem-db.rb +94 -0
- data/examples/require_me.rb +11 -0
- data/examples/requires.rb +42 -0
- data/examples/schema-info.rb +34 -0
- data/ext/amalgalite/c/amalgalite.c +355 -0
- data/ext/amalgalite/c/amalgalite.h +151 -0
- data/ext/amalgalite/c/amalgalite_blob.c +240 -0
- data/ext/amalgalite/c/amalgalite_constants.c +1432 -0
- data/ext/amalgalite/c/amalgalite_database.c +1188 -0
- data/ext/amalgalite/c/amalgalite_requires_bootstrap.c +282 -0
- data/ext/amalgalite/c/amalgalite_statement.c +649 -0
- data/ext/amalgalite/c/extconf.rb +71 -0
- data/ext/amalgalite/c/gen_constants.rb +353 -0
- data/ext/amalgalite/c/notes.txt +134 -0
- data/ext/amalgalite/c/sqlite3.c +243616 -0
- data/ext/amalgalite/c/sqlite3.h +12894 -0
- data/ext/amalgalite/c/sqlite3_options.h +4 -0
- data/ext/amalgalite/c/sqlite3ext.h +705 -0
- data/lib/amalgalite/3.1/amalgalite.so +0 -0
- data/lib/amalgalite/aggregate.rb +73 -0
- data/lib/amalgalite/blob.rb +186 -0
- data/lib/amalgalite/boolean.rb +42 -0
- data/lib/amalgalite/busy_timeout.rb +47 -0
- data/lib/amalgalite/column.rb +99 -0
- data/lib/amalgalite/core_ext/kernel/require.rb +21 -0
- data/lib/amalgalite/csv_table_importer.rb +75 -0
- data/lib/amalgalite/database.rb +933 -0
- data/lib/amalgalite/function.rb +61 -0
- data/lib/amalgalite/index.rb +43 -0
- data/lib/amalgalite/memory_database.rb +15 -0
- data/lib/amalgalite/packer.rb +231 -0
- data/lib/amalgalite/paths.rb +80 -0
- data/lib/amalgalite/profile_tap.rb +131 -0
- data/lib/amalgalite/progress_handler.rb +21 -0
- data/lib/amalgalite/requires.rb +151 -0
- data/lib/amalgalite/schema.rb +225 -0
- data/lib/amalgalite/sqlite3/constants.rb +95 -0
- data/lib/amalgalite/sqlite3/database/function.rb +48 -0
- data/lib/amalgalite/sqlite3/database/status.rb +68 -0
- data/lib/amalgalite/sqlite3/status.rb +60 -0
- data/lib/amalgalite/sqlite3/version.rb +55 -0
- data/lib/amalgalite/sqlite3.rb +6 -0
- data/lib/amalgalite/statement.rb +421 -0
- data/lib/amalgalite/table.rb +91 -0
- data/lib/amalgalite/taps/console.rb +27 -0
- data/lib/amalgalite/taps/io.rb +74 -0
- data/lib/amalgalite/taps.rb +2 -0
- data/lib/amalgalite/trace_tap.rb +35 -0
- data/lib/amalgalite/type_map.rb +63 -0
- data/lib/amalgalite/type_maps/default_map.rb +166 -0
- data/lib/amalgalite/type_maps/storage_map.rb +38 -0
- data/lib/amalgalite/type_maps/text_map.rb +21 -0
- data/lib/amalgalite/version.rb +8 -0
- data/lib/amalgalite/view.rb +26 -0
- data/lib/amalgalite.rb +51 -0
- data/spec/aggregate_spec.rb +158 -0
- data/spec/amalgalite_spec.rb +4 -0
- data/spec/blob_spec.rb +78 -0
- data/spec/boolean_spec.rb +24 -0
- data/spec/busy_handler.rb +157 -0
- data/spec/data/iso-3166-country.txt +242 -0
- data/spec/data/iso-3166-schema.sql +22 -0
- data/spec/data/iso-3166-subcountry.txt +3995 -0
- data/spec/data/make-iso-db.sh +12 -0
- data/spec/database_spec.rb +505 -0
- data/spec/default_map_spec.rb +92 -0
- data/spec/function_spec.rb +78 -0
- data/spec/integeration_spec.rb +97 -0
- data/spec/iso_3166_database.rb +58 -0
- data/spec/json_spec.rb +24 -0
- data/spec/packer_spec.rb +60 -0
- data/spec/paths_spec.rb +28 -0
- data/spec/progress_handler_spec.rb +91 -0
- data/spec/requires_spec.rb +54 -0
- data/spec/rtree_spec.rb +66 -0
- data/spec/schema_spec.rb +131 -0
- data/spec/spec_helper.rb +48 -0
- data/spec/sqlite3/constants_spec.rb +108 -0
- data/spec/sqlite3/database_status_spec.rb +36 -0
- data/spec/sqlite3/status_spec.rb +22 -0
- data/spec/sqlite3/version_spec.rb +28 -0
- data/spec/sqlite3_spec.rb +53 -0
- data/spec/statement_spec.rb +168 -0
- data/spec/storage_map_spec.rb +38 -0
- data/spec/tap_spec.rb +57 -0
- data/spec/text_map_spec.rb +20 -0
- data/spec/type_map_spec.rb +14 -0
- data/spec/version_spec.rb +8 -0
- data/tasks/custom.rake +101 -0
- data/tasks/default.rake +244 -0
- data/tasks/extension.rake +28 -0
- data/tasks/this.rb +208 -0
- 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
|
+
|
data/bin/amalgalite-pack
ADDED
@@ -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
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
|
+
|