Version: 1.0.0
By: Gab!
Introduction
Allows you to create SQL databases locally, without server or Internet connection, of course, also will not allow others to see what's in this file unless you send it to them.
Features
- SQL's Syntax
- SQLite.org's API
- Fast
Screenshots
Imperceptible by images
Demo
Not necessary, I think.
Script
Code:
#==============================================================================
# ** SQLiteAPI
#------------------------------------------------------------------------------
# Importa todas as funções usadas pelo script, a partir da DLL do SQLite3
#==============================================================================
module SQLiteAPI
FUNCTIONS = {}
DLL = "System/SQLite3"
# Prototypes
[
[:open, "PP", "L"], [:prepare, "LPIPI", "I"],
[:sleep, "L", "I"], [:step, "L", "I"],
[:column_count, "L", "I"], [:column_name, "LI", "P"],
[:column_type, "LI", "I"], [:column_text, "LI", "P"],
[:column_int, "LI", "I"], [:column_double, "LI", "D"],
[:finalize, "L", "I"], [:close, "L", "I"],
[:backup_init, "LPLP", "L"], [:backup_step, "LI", "I"],
[:backup_finish, "L", "I"], [:errmsg, "L", "P"]
].each{|func, arg1, arg2|
FUNCTIONS[func] = Win32API.new(DLL, "sqlite3_#{func.to_s}", arg1, arg2)
define_method(func){|*args| FUNCTIONS[func].call(*args) }
module_function(func)
}
# Constantes
SQLITE_OK = 0
SQLITE_ERROR = 1
SQLITE_INTERNAL = 2
SQLITE_PERM = 3
SQLITE_ABORT = 4
SQLITE_BUSY = 5
SQLITE_LOCKED = 6
SQLITE_NOMEM = 7
SQLITE_READONLY = 8
SQLITE_INTERRUPT = 9
SQLITE_IOERR = 10
SQLITE_CORRUPT = 11
SQLITE_NOTFOUND = 12
SQLITE_FULL = 13
SQLITE_CANTOPEN = 14
SQLITE_PROTOCOL = 15
SQLITE_EMPTY = 16
SQLITE_SCHEMA = 17
SQLITE_TOOBIG = 18
SQLITE_CONSTRAINT = 19
SQLITE_MISMATCH = 20
SQLITE_MISUSE = 21
SQLITE_NOLFS = 22
SQLITE_AUTH = 23
SQLITE_FORMAT = 24
SQLITE_RANGE = 25
SQLITE_NOTADB = 26
SQLITE_NOTYPE = 27
SQLITE_ROW = 100
SQLITE_DONE = 101
SQLITE_INTEGER = 1
SQLITE_FLOAT = 2
SQLITE_TEXT = 3
SQLITE_BLOB = 4
SQLITE_NULL = 5
# Função auxiliar
module_function
def errorStr(id)
case id
when SQLITE_OK; "Sem erros"
when SQLITE_ERROR; "Erro na consulta ou database não encontrada"
when SQLITE_INTERNAL; "Erro interno no SQLite"
when SQLITE_PERM; "Permissão de acesso negada"
when SQLITE_ABORT; "Rotina de callback abortou"
when SQLITE_BUSY; "Arquivo de database trancado"
when SQLITE_LOCKED; "Uma tabela na database está trancada"
when SQLITE_NOMEM; "Falha ao alocar memória"
when SQLITE_READONLY; "Tentativa de escrever na database somente-leitura"
when SQLITE_INTERRUPT; "Operação terminada pelo sqlite3_interrupt()"
when SQLITE_IOERR; "Algum tipo de erro de I/O ocorreu"
when SQLITE_CORRUPT; "A imagem da database está corrompida"
when SQLITE_NOTFOUNT; "Código desconhecido para sqlite3_file_control()"
when SQLITE_FULL; "Inserção falhou por que a database está cheia"
when SQLITE_CANTOPEN; "Não foi possível abrir o arquivo da database"
when SQLITE_PROTOCOL; "Protocolo para trancar a database falhou"
when SQLITE_EMPTY; "A database está vazia"
when SQLITE_SCHEMA; "A database mudou seu esquema"
when SQLITE_TOOBIG; "A String ou BLOB excedeu o limite de tamanho"
when SQLITE_CONSTRAINT; "Abortar devido à violação de restrição"
when SQLITE_MISMATCH; "Tipos de dados incompatíveis"
when SQLITE_MISUSE; "Biblioteca usada incorretamente"
when SQLITE_NOLFS; "Uso de recursos do sistema operacional não suportado no host"
when SQLITE_AUTH; "Autorização negada"
when SQLITE_FORMAT; "Erro de formato da database auxiliar"
when SQLITE_RANGE; "2º parâmetro para o sqlite3_bind() fora da faixa"
when SQLITE_NOTABD; "Arquivo aberto não é uma database"
when SQLITE_NOTYPE; "Tipo de dado desconhecido"
when SQLITE_ROW; "sqlite3_step() tem outra linha pronta"
when SQLITE_DONE; "sqlite3_step() terminou a execução"
end
end
# Classe de erros
class Error < Exception
end
end
#==============================================================================
# ** SQLite
#------------------------------------------------------------------------------
# Classe que gerencia as conexões com databases de modo orientado.
#==============================================================================
class SQLite
#-------------------------------------------------------------------------
# * Atributos
#-------------------------------------------------------------------------
attr_reader :closed
attr_reader :filename
#-------------------------------------------------------------------------
# * Inicialização
# + filename : Nome de criação da database
#-------------------------------------------------------------------------
def initialize(filename = "#{__FILE__}.db")
# Variáveis de instância
@filename = filename
@closed = false
@queryRunning = false
# Abre a conexão
db = [0].pack("L*")
SQLiteAPI.open(filename, db);
@handle = db.unpack("L*").first
# Define o finalizador para liberar o acesso à database
ObjectSpace.define_finalizer(self, method(:finalize))
end
#-------------------------------------------------------------------------
# * Faz uma query recursiva para um bloco, retornando para ele cada linha
# que foi recebida pela query.
# + str : Query SQL
# + &block : Bloco que receberá as linhas (deve receber um argumento)
#-------------------------------------------------------------------------
def recursiveQuery(str, &block)
# Retorna se já estiver rodando uma query
return if @queryRunning
# Checa se o arquivo está aberto
check_closed
@queryRunning = true
# Prepara a query
buffer = [0].pack("L*")
rc = SQLiteAPI.prepare(@handle, str, -1, buffer, 0)
query = buffer.unpack("L*").first
# Se houver problemas, finaliza e imprime o erro (sintaxe, tabela inexistente, etc)
if (rc != SQLiteAPI::SQLITE_OK)
raise(SQLiteAPI::Error, SQLiteAPI.errorStr(rc).force_encoding("ASCII-8BIT") + ": " + SQLiteAPI.errmsg(@handle).to_s, caller)
end
# Roda um passo da query (geralmente retornando uma linha)
rc = SQLiteAPI.step(query)
while (rc != SQLiteAPI::SQLITE_DONE)
case rc
when SQLiteAPI::SQLITE_BUSY # Se não estiver completa, espera 0.01s.
SQLiteAPI.sleep(10)
when SQLiteAPI::SQLITE_ERROR # Se houver erro, imprime o erro e finaliza
raise(SQLiteAPI::Error, SQLiteAPI.errmsg(@handle).to_s, caller)
break;
when SQLiteAPI::SQLITE_ROW # Se tiver recebido corretamente a linha, trata
rowdata = {}
# Contagem de colunas
n = SQLiteAPI.column_count(query)
# Recebimento da linha
n.times{|i|
# Nome e tipo da coluna
name = SQLiteAPI.column_name(query, i).to_s
type = SQLiteAPI.column_type(query, i).to_i
case type
when SQLiteAPI::SQLITE_INTEGER # Inteiro
rowdata[name] = SQLiteAPI.column_int(query, i).to_i
when SQLiteAPI::SQLITE_FLOAT # Float
rowdata[name] = SQLiteAPI.column_double(query, i).to_f
when SQLiteAPI::SQLITE_TEXT # Texto
rowdata[name] = SQLiteAPI.column_text(query, i).to_s
when SQLiteAPI::SQLITE_BLOB # Blob (não implementado)
warn("Leitura de BLOB não implementada")
rowdata[name] = ""
when SQLiteAPI::SQLITE_NULL # Nulo
rowdata[name] = nil
else # Desconhecido
raise(SQLiteAPI::Error, SQLiteAPI.errorStr(SQLiteAPI::NOTYPE), caller)
end
}
# Retorna a linha para o bloco
# Se o retorno for :breakRecursiveQuery, para a execução
break if block.call(rowdata) == :breakRecursiveQuery
end
# Adquire informação sobre a próxima informação
rc = SQLiteAPI.step(query)
end
# Habilita novamente a rodar queries
@queryRunning = false
# Finaliza processo atual
SQLiteAPI.finalize(query)
# Retorna o próprio objeto
return self
end
#-------------------------------------------------------------------------
# * Faz uma query não recursiva e retorna todas as linhas recebidas
# + str : Query a ser executada
#-------------------------------------------------------------------------
def query(str)
data = []
recursiveQuery(str){|rowdata| data << rowdata}
return data
end
#-------------------------------------------------------------------------
# * Retorna todas as tabelas existentes na database
#-------------------------------------------------------------------------
def getTables
# Checa se a database está aberta
check_closed
# Seleciona todas as tabelas
tabs = []
recursiveQuery("SELECT name FROM sqlite_master WHERE type = 'table'"){|data|
# Armazena seu nome
tabs << data['name']
}
# Retorna nomes
return tabs
end
#-------------------------------------------------------------------------
# * Retorna colunas existentes na tabela
# + table : Tabela que será consultada
#-------------------------------------------------------------------------
def getColumns(table)
# Checa se a database está aberta
check_closed
columns = []
# Seleciona valores quaisquer
buffer = [0].pack("L*")
rc = SQLiteAPI.prepare(@handle, "SELECT * FROM `#{table}`", -1, buffer, 0)
query = buffer.unpack("L*").first
# Faz a contagem de colunas
n = SQLiteAPI.column_count(query)
# Adquire nome das colunas
n.times{|i| columns << SQLiteAPI.column_name(query, i).to_s }
# Finaliza query
SQLiteAPI.finalize(query)
# Retorna colunas compactadas (sem nil) e únicas.
return columns.compact.uniq
end
#-------------------------------------------------------------------------
# * Realiza backup da database para um arquivo
# + to : Arquivo de destino ou outra instância de database
#-------------------------------------------------------------------------
def backup(to = ("Backup#{__FILE__}#{Time.new.to_i}.db"))
# Checa se a database está aberta
check_closed
if (to.is_a?(String))
# Abre uma handle para o arquivo de destino
db = [0].pack("L*")
SQLiteAPI.sqlite3_open(to, db);
toHandle = db.unpack("L*").first
elsif (to.is_a?(SQLite))
# Copia para outra database aberta
toHandle = to.instance_eval{@handle}
else
# Não suportado
raise(ArgumentError, "Tipo não suportado: #{to.class}")
end
# Inicia backup
pBackup = SQLiteAPI.sqlite3_backup_init(toHandle, "main", @handle, "main")
# Faz o backup, se houver permissão/necessidade
if (pBackup != SQLiteAPI::SQLITE_OK)
SQLiteAPI.sqlite3_backup_step(pBackup, -1);
SQLiteAPI.sqlite3_backup_finish(pBackup);
end
# Fecha a handle da memória
SQLiteAPI.sqlite3_close(toHandle)
# Retorna o próprio objeto
return self
end
#-------------------------------------------------------------------------
# * Faz o contrário do backup: Copia de um arquivo para a database atual
# + file : Arquivo lido para recuperar a database atual
#-------------------------------------------------------------------------
def restore(file)
# Checa se a database não foi fechada
check_closed
# Restora
tmpClose{SQLite.new(file).backup(self).finalize}
# Retorna a própria database
return self
end
#-------------------------------------------------------------------------
# * Fecha temporáriamente a database para permitir acesso ao arquivo
# + &block : Bloco que será rodado enquanto a database estiver fechada
#-------------------------------------------------------------------------
def tmpClose(&block)
# Checa se o arquivo já não está fechado
check_closed
# Fecha a database
SQLiteAPI.sqlite3_close(@handle)
# Determina que o arquivo já está fechado
@closed = true
# Chama o bloco
block.call
# Abre novamente a handle para a database
@closed = false
db = [0].pack("L*")
SQLiteAPI.sqlite3_open(@filename, db);
@handle = db.unpack("L*").first
# Retorna a própria database
return self
end
#-------------------------------------------------------------------------
# * Finaliza a database, fechando a conexão com o arquivo
#-------------------------------------------------------------------------
def finalize
SQLiteAPI.sqlite3_close(@handle)
@closed = true
@handle = 0
end
private
#-------------------------------------------------------------------------
# * Checa se o arquivo está fechado. Caso esteja, chama a excessão
# SQLiteAPI::Error, para previnir acessos inválidos.
#-------------------------------------------------------------------------
def check_closed
return unless @closed
raise(SQLiteAPI::Error, "Database fechada!", caller)
end
end
#==============================================================================
# ** MemorySQLiteAPI
#------------------------------------------------------------------------------
# Classe para iniciar database na memória. Suporta todos os métodos da SQLite
#==============================================================================
class MemorySQLite < SQLite
#-------------------------------------------------------------------------
# * Inicialização
#-------------------------------------------------------------------------
def initialize
super(':memory:')
end
#-------------------------------------------------------------------------
# * Como é um bloco na memória, não são necessários processamentos adicio-
# nais para permitir acesso ao arquivo.
#-------------------------------------------------------------------------
def tmpClose(&block)
# Checa se a conexão está ativa
check_closed
# Chama o block
block.call
# Retona a própria database
return self
end
#-------------------------------------------------------------------------
# * Retira a definição do método filename, para que não ocorra uma tenta-
# tiva de acesso à um arquivo inexistente.
#-------------------------------------------------------------------------
undef filename
end
Instructions
- Put SQLite3.dll (download here) on Project's "System" folder.
- Put script above "Main" script.
- To developers:
Code:
# Create new DB file. If already exists, open it.
# To create a memory database (no file), use MemorySQLite instead SQLite with no arguments.
sqlite = SQLite.new("File.db")
# Doing queries
sqlite.query("CREATE TABLE `TestTable` (C1 INT(9), C2 TEXT)")
sqlite.query("INSERT INTO `TestTable` VALUES (2012, 'ABCD')")
sqlite.query("INSERT INTO `TestTable` VALUES (2013, 'EFGH')")
# Recursive Query
sqlite.recursiveQuery("SELECT * FROM `TestTable`"){|line|
print line["C1"] # First loop: 2012 // Second loop: 2013
print line["C2"] # First loop: ABCD // Second loop: EFGH
}
# Get database tables
sqlite.getTables # => ["TestTable"]
# Get table's columns
sqlite.getColumns("TestTable") # => ["C1", "C2"]
# Backup
sqlite.backup("NewDBFile.db") # Save all on "NewDBFile.db"
# Backup to another instance
tmpsql = SQLite.new("OtherDB.db")
tmpsql.getTables # => []
sqlite.backup(tmpsql)
tmpsql.getTables # => ["TestTable"]
# Restore backup
sqlite.restore("NewDBFile.db") # Copy all from "NewDBFile.db" to open instance
# Temporarily close
sqlite.tmpClose{
print "Block running with db closed"
}
# Finalize
sqlite.finalize
Compatibility
- RPG Maker VX Ace
I'll improve compatibility with VX n XP next days. It's because some Ruby 1.9 methods.
Credits and Thanks
SQLite.org - DLL & API
Author's Notes
Any problem with this script feel free to contact me by pm or email (gab.teles@hotmail.com).
Terms and Conditions
- Free for commercial use. It only apply to script, look at SQLite Copyright to see about it's license.