Skip to content

Commit

Permalink
database/sql: add WebSQL wrapping
Browse files Browse the repository at this point in the history
  • Loading branch information
meh committed Feb 3, 2014
1 parent 93aec20 commit 0dcfc89
Show file tree
Hide file tree
Showing 2 changed files with 324 additions and 0 deletions.
194 changes: 194 additions & 0 deletions opal/browser/database/sql.rb
@@ -0,0 +1,194 @@
require 'promise'
require 'ostruct'

module Browser; module Database

class SQL
# Check if the browser supports WebSQL.
def self.supported?
Browser.supports? 'WebSQL'
end

class Error < StandardError
def self.new(error)
return super if self != Error

[Unknown, Database, Version, TooLarge, Quota, Syntax, Constraint, Timeout] \
[`error.code`].new(`error.message`)
end

Unknown = Class.new(self)
Database = Class.new(self)
Version = Class.new(self)
TooLarge = Class.new(self)
Quota = Class.new(self)
Syntax = Class.new(self)
Constraint = Class.new(self)
Timeout = Class.new(self)
end

include Native

# @return [String] the name of the database
attr_reader :name

# @return [String] the description for the database
attr_reader :description

# @return [Integer] the size constraint in bytes
attr_reader :size

# Open a database with the given name and options.
#
# @param name [String] the name for the database
# @param options [Hash] options to open the database
#
# @option options [String] :description the description for the database
# @option options [String] :version ('') the expected version of the database
# @option options [Integer] :size (5 * 1024 * 1024) the size constraint in bytes
def initialize(name, options = {})
@name = name
@description = options[:description] || name
@version = options[:version] || ''
@size = options[:size] || 5 * 1024 * 1024

super(`window.openDatabase(#{name}, #{@version}, #{@description}, #{@size})`)
end

# @overload version()
#
# Get the version of the database.
#
# @return [String]
#
# @overload version(from, to, &block)
#
# Migrate the database to a new version.
#
# @param from [String] the version you're migrating from
# @param to [String] the version you're migrating to
#
# @yieldparam transaction [Transaction] the transaction to work with
def version(from = nil, to = nil, &block)
return `#@native.version` unless block

`#@native.changeVersion(#{from}, #{to},
#{-> t { block.call(Transaction.new(self, t)) }})`
end

# Start a transaction on the database.
#
# @yieldparam transaction [Transaction] the transaction to work on
def transaction(&block)
raise ArgumentError, 'no block given' unless block

`#@native.transaction(#{-> t { block.call(Transaction.new(self, t)) }})`
end

# Allows you to make changes to the database or read data from it.
class Transaction
include Native

# @return [Database] the database the transaction has been created from
attr_reader :database

# @private
def initialize(database, transaction)
@database = database

super(transaction)
end

# Query the database.
#
# @param query [String] the SQL query to send
# @param parameters [Array] optional bind parameters for the query
#
# @return [Promise]
def query(query, *parameters)
promise = Promise.new

`#@native.executeSql(#{query}, #{parameters},
#{-> _, r { promise.resolve(Result.new(self, r)) }},
#{-> _, e { promise.reject(Error.new(e)) }})`

promise
end
end

# Represents a row.
class Row < OpenStruct
# @private
def initialize(row)
super(Hash.new(row))
end

def inspect
"#<SQL::Row: #{Hash.new(@native).inspect}>"
end
end

class Result
include Native

# @return [Transaction] the transaction the result came from
attr_reader :transaction

# @return [SQL] the database the result came from
attr_reader :database

# @private
def initialize(transaction, result)
@transaction = transaction
@database = transaction.database

super(result)
end

include Enumerable

# Get a row from the result.
#
# @param index [Integer] the index for the row
#
# @return [Row]
def [](index)
if index < 0
index += length
end

unless index < 0 || index >= length
Row.new(`#@native.rows.item(index)`)
end
end

# Enumerate over the rows.
#
# @yieldparam row [Row]
#
# @return [self]
def each(&block)
return enum_for :each unless block

%x{
for (var i = 0, length = #@native.rows.length; i < length; i++) {
#{block.call(self[`i`])};
}
}

self
end

# @!attribute [r] length
# @return [Integer] number of rows in the result
def length
`#@native.rows.length`
end

# @!attribute [r] affected
# @return [Integer] number of affected rows
alias_native :affected, :rowsAffected
end
end

end; end
130 changes: 130 additions & 0 deletions spec/database/sql_spec.rb
@@ -0,0 +1,130 @@
require 'spec_helper'
require 'browser/database/sql'

describe Browser::Database::SQL do
SQL = Browser::Database::SQL

describe '#new' do
it 'sets the attributes properly' do
sql = SQL.new('test', description: 'trains', size: 1024)

expect(sql.name).to eq('test')
expect(sql.description).to eq('trains')
expect(sql.size).to eq(1024)
end

it 'sets the version properly' do
sql = SQL.new('test2', version: '1.0')
expect(sql.version).to eq('1.0')

sql = SQL.new('test')
expect(sql.version).to eq('')
end
end

describe '#transaction' do
async 'calls the block with the transaction' do
sql = SQL.new('test')

sql.transaction {|t|
async {
expect(t).to be_a(SQL::Transaction)
}
}
end

async 'the transaction database is the right one' do
sql = SQL.new('test')

sql.transaction {|t|
async {
expect(t.database).to eq(sql)
}
}
end
end

describe SQL::Transaction do
describe '#query' do
async 'returns a promise' do
sql = SQL.new('test')

sql.transaction {|t|
async {
expect(t.query('hue')).to be_a(Promise)
}
}
end

async 'resolves on success' do
sql = SQL.new('test')

sql.transaction {|t|
t.query('CREATE TABLE IF NOT EXISTS test(ID INTEGER PRIMARY KEY ASC, a TEXT)').then {|r|
async {
expect(r).to be_a(SQL::Result)
}
}
}
end

async 'rejects on failure' do
sql = SQL.new('test')

sql.transaction {|t|
t.query('huehue').rescue {|e|
async {
expect(e).to be_a(SQL::Error::Syntax)
}
}
}
end
end
end

describe SQL::Result do
describe '#length' do
async 'has the proper length' do
sql = SQL.new('test')

sql.transaction {|t|
t.query('SELECT 1').then {|r|
async {
expect(r.length).to eq(1)
}
}
}
end
end

describe '#[]' do
async 'returns a row' do
sql = SQL.new('test')

sql.transaction {|t|
t.query('SELECT 1, 2, 3').then {|r|
async {
expect(r[0]).to be_a(SQL::Row)
expect(r[-1]).to be_a(SQL::Row)

expect(r[0]).to eq(r[-1])
}
}
}
end

async 'returns nil on missing row' do
sql = SQL.new('test')

sql.transaction {|t|
t.query('SELECT 1, 2, 3').then {|r|
async {
expect(r[5]).to be_nil
expect(r[-5]).to be_nil
}
}
}
end
end
end
end if Browser::Database::SQL.supported?

0 comments on commit 0dcfc89

Please sign in to comment.