Manual do KirbyBase

Versão 1.9

Escrito originalmente por Jamey Cribbs
jcribbs@twmi.rr.com
Traduzido para o português por Murtog e Carlos Roberto
murtog@[[NOSPAM]]gmail.com e crncosta@[[NOSPAM]]yahoo.com.br

Introdução

KirbyBase é um banco de dados simples - escrito totalmente em Python - e que usa um arquivo texto único para armazenar as informações. Algumas de suas principais características são:


O KirbyBase atende aos casos em que ficamos entre o arquivo texto puro e pequenos banco de dados - como o SQLite ( http://www.sqlite.org ) e o Gadfly ( http://gadfly.sourceforge.net ) - para armazenar nossas informações. Dentro desse escopo ele é realmente útil e funcional.

Se você achou utilidade para o KirbyBase, está aplicando ele em algum projeto, ele te salvou o emprego, por favor, mande-me um e-mail informando. Essa é a maior recompensa e motivação para que eu continue meu trabalho no KirbyBase.



Conectando a um banco de dados

Para usar o KirbyBase o primeiro passo é importar o módulo:

from kirbybase import
KirbyBase, KBError

Feito isso, partimos para criar uma instância*:
db = KirbyBase()
------------------
* Essa instância, sem referência a ip, porta e tal, é usada quando o arquivo
banco de dados está no disco rígido, o mesmo disco que a aplicação que vai usá-lo.
Por padrão, a instância é local e usa o mesmo espaço de memória que a sua aplicação. Para especificar uma conexão cliente/servidor, ficaria algo mais ou menos assim:
db = KirbyBase('client', '192.168.0.10', '44444')
- Lembra que eu disse que só era necessário alterar uma linha ?
Logicamente, você substituirá o endereço IP e a porta pelos valores do seu servidor e pela porta em que o KirbyBase está rodando.

A chamada desse método retornará uma referência a instância de KirbyBase.

Criando uma nova tabela

Para criar uma nova tabela, você especifica o nome do arquivo local - incluindo o caminho ( path ) - que servirá para armazenar a tabela, e uma lista contendo os nomes dos campos e seus respectivos tipos. Por exemplo, para criar uma tabela com informações de aviões da Segunda Guerra Mundial:

result = db.create('plane.tbl', ['name:str', 'country:str', 'speed:int', 'range:int',
'began_service:datetime.date'])

Se você precisar armazenar a tabela em um diretório diferente do que o corrente, ponha-o em um diretório chamado db dentro do diretório corrente, por exemplo ( incluir o caminho do arquivo da tabela, serve para todos os métodos do KirbyBase ):

result = db.create('./db/plane.tbl', ['name:str', 'country:str', 'speed:int', 
'range:int','began_service:datetime.date'])

Observe que na lista de campos você separa o nome do campo do tipo do campo através dos dois pontos ( ":" ).  O KirbyBase criará automaticamente uma chave primária chamada 'recno' para cada tabela ( é o Id ao qual me referi acima ). Esse campo, esse Id, irá se auto-incrementar sempre que um novo dado for adicionado. Você pode usá-lo nos métodos insert, delete e update, mas você não pode removê-lo sem contudo remover o registro.

Os tipos de campo permitidos no KirbyBase atualmente são str, int, float, bool, datetime.date e datetime.datetime. Para ter compatibilidade entre as versões do KirbyBase para Python e para Ruby, você pode usar os seguintes tipos: String, Integer, Float, Boolean, Date, e DateTime. Use esses tipos se sua intenção for usar suas tabelas tanto no Python quanto no Ruby, usando suas respectivas versões do KirbyBase.

A chamada ao método create mencionada anteriormente retornará True, se a tabela for criada com sucesso.



O método insert

Para inserir dados na tabela, use o método insert

recno = db.insert('plane.tbl', ['P-51', 'USA', 403, 1201, date.datetime(1943,05,27)])

O tamanho da lista que representa os dados deve coincidir com o da lista de campos ( exclui-se nesse caso o campo "recno", pois ele é uma chave primária que define seu próprio valor ). E ainda, os tipos também devem coincidir. No exemplo acima, pôr '403', no lugar de 403, resultaria num erro.

Você também pode usar um dicionário para definir o valor dos campos. Reescrevendo o exemplo acima para usar um dicionários, ao invés de duas listas, ficaria da seguinte maneira:

recno = db.insert('plane.tbl', {'name': 'P-51', 'country':'USA','speed': 403, 'range':1201, 
'began_service':date.datetime(1943,05,27)})

Você também pode usar uma instância de objeto para definir os valores dos campos. Para cada campo do dado a ser inserido, deve corresponder um atributo na instância do objeto, esse atributo - logicamente - com o valor que você quer adicionar no campo da tabela. Reescrevendo o exemplo acima para usar um objeto ao invés de um dicionário, ficaria dessa maneira:

class Record(object): pass

rec = Record()
rec.name = 'P-51'
rec.country = 'USA'
rec.speed = 403
rec.range = 1201
rec.began_service = datetime.date(1943,05,27)

recno = db.insert('plane.tbl', rec)

A chamada ao método insert retornará a Id ( "recno" ) recebida pelo novo dado inserido na tabela. Esse é um número gerado automaticamente pelo KirbyBase. Esse número nunca mudará, ele sempre será referente ao dado recém adicionado, podendo ser usado posteriormente como referência.



O método insertBatch

Para inserir vários dados de uma só vez na tabela você deve usar o método insertBatch:

recsToInsert =[
['P-51', 'USA', 403, 1201,
date.datetime(1943,05,27)],

['P-47', 'USA',
379, 805, date.datetime(1942,01,11)]

]
recnoList = db.insertBatch('plane.tbl', recsToInsert)

Esse método aceita uma lista de dicionários ou uma lista de listas ou uma lista de ambos, sendo que cada elemento corresponde a um novo registro a ser inserido na tabela.

O método insertBatch retorna uma lista contendo os Id's obtidos por cada novo registro criado pelo KirbyBase.



Como selecionar dados

A sintaxe que você deve usar para selecionar registros em tabelas ( query's ) é a mesma para os métodos insert, delete e update. Portanto, vou explicar primeiramente como formular essas query's e depois passamos a explicar cada método separadamente.

Para todos os tipos, exceto string e booleans, você pode construir uma query usando operadores de comparação da própria linguagem Python. Para campos string, existem duas maneiras de voce fazer a sua query: (1) usando texto, isto é, fazendo uma busca exata por determinada palavra, frase ou caracter (2) usando uma expressão regular, e com esse recurso conseguir fazer buscas mais "amplas". Para campos do tipo boolean, você usa a busca por texto exato ( claro, na prática você só buscará ou por True ou por False, nada além disso no que diz referência a esses campos ).

Aqui está uma tabela com o tipo de campo e as possíveis maneiras de fazer querys para eles:

Tipo Sintaxe permitida
int ==, =>, <=, >, <, <>, !=
float ==, =>, <=, >, <, <>, !=
date ==, =>, <=, >, <, <>, !=
datetime ==, =>, <=, >, <, <>, !=
str busca exata (i.e. 'John'), expressão regular (i.e. '^John$')
bool True, False
Selecionando para os tipos: int, float, date e datetime

Primeiramente, vamos discutir o uso dos operadores simples de comparação da linguagem Python. Isso inclui: ==, =>, <=, >, <, <> e !=. Em cada um dos métodos insert, update e delete você simplesmente põe o operador de comparação entre aspas, juntamente com o valor a ser comparado (i.e. '>300'). Em vez de ficar fazendo hard-code e pôr números a cada comparação, você pode usar o operador de formatação de Python para poder usar variáveis no seu código (i.e. '>%d' % myvar). Essa sintaxe funciona para todos os tipos de dados, exceto strings e booleans.

Selecionado para o tipo str

Para campos do tipo string, você pode selecionar entradas simplesmente indicando valores aos quais elas devem coincidir exatamente . Por exemplo, se você quisesse selecionar todos as entradas onde o primeiro nome é "John", você simplesmente poderia usar o valor 'John'. Isso selecionaria todos "Johns", porém nenhum dos "Johnnys". Esse é o comportamento padrão para seleções em campos string. Nota: Essa é uma mudançã da versão 1.7. Para versões anteriores a 1.7 o comportamento padrão era selecionar entradas usando expressões regulares, o que será descrito adiante.


Existe uma outra maneira de selecionar entradas do tipo string: usando as expressões regulares de Python. Qualquer expressão regular válida servirá. Logo, se , por exemplo, eu quiser buscar todas as entradas em que o nome começa por "John", eu posso expressar a busca da seguinte maneira: '^John'. Isto retornaria todas as entradas onde o nome completo começa com "John", ex: i.e. "John Jones", "Johnny Doe", mas, por causa do símbolo ^, não retornaria algo como: "Jack Johnson". Para incluir também essa entrada no resultado, você teria que mudar a expressão apenas para 'John'. Para habilitar o uso de expressões regulares em suas buscas você deverá passar o seguinte dado na chamada da função: useRegExp=True.

Você deve estar se perguntando, "Se está correto usar na seleção de um campo int ou date '==45', porque eu não posso fazer algo como '==John' para selecionar entradas string? Porque para seleções envolvendo string eu devo usar 'John' (ou '^John$' se useRegExp=True)? Bem, a resposta é que para todo tipo de campo exceto campos-string, o KirbyBase sabe distinguir o que é um operador do que é o valor. Em outras palavras, tome - por exemplo - a expressão '==45'. Se ela estiver sendo usando para um campo inteiro, KirbyBase saberá que o '==' é o operador que significa "é igual" e que 45 é o valor a ser usado nas comparações. Mas e se a mesma expressão '==45' é usada para um campo string? O KirbyBase ficará sem saber se o usuário está buscando entradas que tenham tal valor igual a '45' ou igual a '==45'. Aí está o porque da não possibilidade de usar operadores para strings. Para ser honesto, eu ainda não parei para trabalhar nisso. Porém tenho certeza de que se isso virar uma necessidade, acharei uma solução elegante para o problema.

Selecionando para o tipo bool

Para campos do tipo bool, você pode especificar o critério de seleção simplesmente comparando o campo com as palavras reservadas ( built-in ) de Python: True ou False.

Múltiplos critérios de seleção

Você pode ter múltiplos critérios de seleção numa query. Por exemplo, para selecionar todos os registros onde temos no primeiro nome "John" e que tenha o salário maior que $50,000, você poderia especifica 'John' e '>50000' na sua expressão de query. Isso será explicado mais claramente nos exemplos do método select, situados mais abaixo.

Selecionando múltiplos registro usando o Id, ou "recno"

Você pode selecionar múltiplos registros simplesmente pondo uma lista dos vários Id's a serem retornados. Você verá um exemplo disso na descrição do método select.

Selecionando todos os registros

Finalmente, existe uma maneira especial de selecionar todos os registros. Na expressão da query, se você usar como campo o 'recno' e como critério de seleção '*', KirbyBase irá retornar todos os registros contidos na tabela.

Agora que entendemos como funciona a seleção/busca de registros, podemos dar uma olhada mais profunda nos métodos select, update e delete.



O método select

O método select nos permite buscar por registros em que o campo X atente ao critério Y,  podem ser vários campos e vários critérios, basta que o número de campos e o número de critérios coincida. Você ainda pode selecionar quais campos dos registros devem ser retornados, ordem como você quer que os campos do resultado sejam listados, e o formato dos registros no resultado ( i.e. lista, dicionário ou objeto ).


Um exemplo simples de chamada ao método select seria:
result = db.select('plane.tbl', ['country'], ['USA'])

Essa chamada retorna todos os registros da tabela que tenham no campo "country" o valor 'USA'. Já que o padrão é não usar expressões regulares, o select irá buscar por registros em que o campo "country" tenha o valor exato: 'USA'. Como nós não especificamos quais campos devem ser retornados no resultado do select, todos os campos do registro serão retornados. E ainda, como nós não especificamos uma ordenação dos resultados, o resultado não terá ordem nenhuma, virá como está na tabela do banco de dados.

Usando expressões regulares

Agora, se nós quisessemos retornar todos os registros que tenham como país ou 'EUA' ou "United States", como fariamos? Uma maneira seria especificar explicitamente ambos na nossa query:

result = db.select('plane.tbl', ['country','country'], '['USA','United States'])

Uma outra maneira seria usar expressões regulares:

result = db.select('plane.tbl', ['country'], ['US|United States'], useRegExp=True)

Observe que deve-se passar a variável useRegExp como o valor setado para True, para, aí sim, o KirbyBase poder fazer buscas usando expressões regulares.

Usando operadores de comparação

Agora vamos exemplificar o uso de operadores de comparação no select. Para selecionar todos os aviões que tem velocidade entre 300 mph e 400 mph, fariamos algo como:

low_speed = 300
high_speed = 400
result = db.select('plane.tbl', ['speed', 'speed'],['>%d' % low_speed, '<%d' % high_speed])

Deve-se notar que para selecionar os dados entre um intervalo, eu apenas utilizei o mesmo campo duas vezes, comparando-o com o menor e o maior valor. Outra coisa que deve ser percebida é que você pode usar o operador Python de substituição de variáveis ( % ).

Selecionando com base em múltiplos campos

E como buscar por aviões que sejam rápidos e tenham longo alcance ( "range" ) ?

result = db.select('plane.tbl', ['speed', 'range'], ['>350','>900'])

Você pode, claro, combinar comparações string e não-string em um mesmo comando select. Isso torna possível, por exemplo, selecionar todos os aviões bombardeiros que tenham velocidade acima de 300mph:

result = db.select('plane.tbl', ['plane_type', 'speed'], ['^Bomber', '>300'], useRegExp=True)

Obeserve que no valor usado para comparar com o "plane_type" ( "tipo de avião" ) nós colocamos o símbolo '^', que indica que nós só queremos tipos de avião que comecem com a palavra "Bomber". Isso garante que não tenhamos registros de avião do tipo 'Fighter-Bomber' no resultado. O símbolo ^ é um metacaractere usada na expressão regular para indicar início de frase, isto é, "^Bomber" retornará todos campos "plane_type" em que o valor começar por Bomber, pode ser: Bomber-blue, Bomber-red, mas não pode ser BBBBomber. Toda a sintaxe de expressão regular funciona no KirbyBase.


Mais informações sobre expressões em geral e sobre o uso de expressões regulares em Python, aqui.



Selecionando com campos do tipo date e datetime

Vamos selecionar registro usando um campo do tipo date. Exemplo, vamos selecionar todos os aviões que entraram em serviço antes de 1940:

result = db.select('plane.tbl', ['began_service'], ['<%s' % datetime.date(1940, 1, 1)])

Obeserve que você usa %s para efetuar as comparações de data. Isso acontece porque o KirbyBase não lida com o date como um objeto, e sim, uma string. Datas são convertidas para string. A razão principal disso é a performance do banco de dados e além do mais, os resultados obtidos são os mesmos, seja usando objetos date, seja usando strings.

Selecionando usando campos bool

Vamos ver um exemplo de como selecionar registros usando campos do tipo bool. O código-exemplo a seguir seleciona todos aviões que ainda estão voando:

result = db.select('plane.tbl', ['still_flying'], [True])
Selecionando pelo ID ou Ids ( Leia-se Id igual a "recno" )

Para selecionar um único registro, usando o seu id ( "recno" ):

result = db.select('plane.tbl', ['recno'], [245])

Para selecionar múltiplos registros usando seus Ids, "recno"´s:

result = db.select('plane.tbl', ['recno'],[2,5,7])
Selecionando todos os registros da tabela
Você pode selecionar todos os registros da tabela usando o 'recno' da seguinte maneira:
result = db.select('plane.tbl', ['recno'],['*'])
Especificando o tipo de objeto a ser retornado no select

O método select retorna uma lista de registros. Cada resultado contido nessa lista pode ser do tipo: lista, dicionário, objeto ou uma string formatada para ser escrita na tela. O tipo padrão é a lista. Por exemplo, para selecionar todos aviões, porém retornar cada registro como um dicionário, fazemos:

result = db.select('plane.tbl', ['recno'], ['*'],returnType='dict')
for record in result:
print record['name']

Agora os registros retornados são objetos:

result = db.select('plane.tbl', ['recno'], ['*'],returnType='object')
for record in result:
print record.name

Se você especificar 'report' na variável returnType, o resultado virá na forma de tabela, útil para impressão. Mais um exemplo, agora usando essa propriedade:

print db.select('plane.tbl', ['recno'], ['*'],['recno','name','country', 'role'],returnType='report')

Fará o resultado do select ser algo como:

recno | name      | country | role
---------------------------------------------
1 | P-51 | USA | Fighter
2 | P-47 | USA | Fighter
3 | B-17 | USA | Bomber
4 | Typhoon | England | Fighter-Bomber
5 | Sptitfire | England | Fighter
6 | Oscar | Japan | Fighter
7 | ME-109 | Germany | Fighter
8 | JU-88 | Germany | Bomber
10 | Zero | Japan | Fighter

Você pode definir quantos registros são impressos antes de um "formfeed" (\f) ser emitido e se linhas pontilhadas são impresssas entre os registros, basta suprir um lista de dois elementos para a variável rptSettings. O primeiro elemento da lista é um número inteiro que indica quantos registros devem ser impressos antes do "formfeed". O padrão é "0" ( zero ), o que indica a não inserção de "formfeeds". O segundo é um boolean ( True ou False ). Se for passado True, linhas pontilhadas serão impressas entre os registros, se falso ( comportamento padrão ) não serão impressas essas linhas.

Limitando os campos no resultado

Por padrão, o resultado do select terá todos os campos. Mas caso você queira que apenas alguns campos sejam retornados nesse resultado, você pode cria uma lista "filtro". Por exemplo, para selecionar todos os aviões que são do "country" "USA", retornando apenas o nome ( "name" ) e a velocidade ( "speed" ) dos registros encontrados:

result = db.select('plane.tbl', ['country'], ['USA'], ['name','speed'])
Ordenando o resultado

Você também talvez queira ordenar seus resultados de acordo com algum campo. O argumento sortFields, que faz essa ordenação, deve ser uma lista com nomes válidos de campos. O resultado será ordenado de acordo com a ordem dos campos na lista passada como argumento. Se nós especificassemos o sortFields como  ['country','role','name'] ordenará o resultado de modo que os aviões de cada país serão agrupados, onde os aviões Alemãos ( Germany ) vindo antes dos Americanos ( USA´s ), e dentro de cada grupo de país, aviões "bombers" serão agrupados antes dos aviões tipo "fighters" e dentro do grupo de aviões "bombers" os registros estarão listados por ordem alfabética. Se você passar uma lista que limita os campos retornados ( parágrafo acima ), a lista de campos listados no sortFields deve pertencer a lista filtro, pois você não pode ordenar algo que não é retornado.

Por exemplo, podemos ordenar os aviões por grupos de países sendo que dentro de cada grupo eles serão ordenados pelo nome:

result = db.select('plane.tbl', ['recno'], ['*'], sortFields=['country','name'])

Se um campo de ordenação é passado, o ordenamento será por padrão ascendente ( do menor para o maior, de a para z ); para ordenar de modo descendente você deve passar uma lista para a variável sortDesc. Nessa lista vão os nomes dos campos que você desejaria ordenar de modo descedente. Sendo que cada integrante dessa lista também deve pertencer a lista passada para a variável sortFields. Essa condição é lógica pois você não pode dizer que quer ordenar algo de modo descendente se você sequer disse que queria ordená-la. Por exemplo, você quer ordenar os resultados por grupos de função ("role") dos aviões, porém deseja que os aviões mais rápidos ("speed") apareçam em primeiro nesses grupos:

result = db.select('plane.tbl', ['recno'], ['*'],sortField=['role', 'speed'],sortDesc=['speed'])


O método update

Para atualizar uma tabela, você precisar definir um critério para seleção dos registros a serem atualizados. Para atualizar um único registro basta você usar o 'recno' dele. Desse modo se procurará exatamente por esse registro, atualizando-se assim no mínimo um registro apenas. Um exemplo disso seria:

result = db.update('plane.tbl', ['recno'], [54], [405], ['speed'])

Nesse exemplos estamos atualizando o registro de 'recno' igual a 54. Nós estamos mudando a velocidade desse registro para 405 mph. Buscas exatas, como as por 'recno', são muito mais rápidas, porque o KirbyBase devolverá o resultado assim que o registro for encontrado, não será preciso buscar por toda a tabela.

Você também pode definir critérios de seleção para múltiplos campos usando expressões regulares ( para campos do tipo string ) e operadores de comparação para os outros tipos ( exceto: string e bool ). Você pode fazer diversas combinações para efetuar as atualizações. Por exemplo, para atualizar todos os aviões americanos ( USA ) com velocidade maior que 400mph:

result = db.update('plane.tbl', ['country', 'speed'], ['USA', '>400'], [1500],['range'])

Nesse exemplo nós alteramos para 1,500 mihas o alcance de todos os aviões americanos com velocidade maior que 400mph.

Atualizando todos os campos do registro de uma só vez

O critério do método update é especificado por uma lista contendo os nomes dos campos e outra lista contendo os valores a serem atualizados nesses campos. Porém, ao invés de ter que digitar todos os campos da tabela numa lista, você pode atualizar todos de uma única vez. Basta não passar a lista referente aos campos a serem atualizados, que no exemplo do parágrafo acima é ['range'], e indicar na lista de valores-atualização ( os valores que substituirão os antigos ) os valores para cada campo da tabela ( exceto o recno, que é imutável ), na mesma ordem e com o mesmo tipo. Um exemplo dessa propriedade seria:

result =db.update('plane.tbl', ['recno'], [106], ['P-47', 'USA', 347, 789,
datetime.date(1942,12,22)])

Lembre-se que a ordem e os tipos da lista de valores-atualização devem coincidir. É importante ressaltar que o campo recno é imutável, portanto não precisa ser passado nessa lista.

Usando o método update com um dicionário

Você também pode usar um dicionário para atualizar uma lista. Reescrevendo o exemplo acima usando um dicionário ficaria da seguinte maneira:

result =db.update('plane.tbl', ['recno'], [106], {'name': 'P-47', 'country':'USA','speed': 347, 
'range': 789, 'began_service':datetime.date(1942,12,22)})

Usando o método update com um objeto

Você também pode usar uma instância de objeto para atualizar os campos. Para cada campo que você deseje atualizar, deve haver um atributo na instância do objeto, sendo que esse atributo deve ser do valor ao qual você deseja atualizar o campo. Reescrevendo o exemplo acima usando objetos ao invés de dicionários ficaria da seguinte maneira:

class Record(object): pass

rec = Record()
rec.name ='P-47'
rec.country = 'USA'
rec.speed = 347
rec.range =789
rec.began_service =datetime.date(1942,12,22)

result = db.update('plane.tbl',['recno'], [106], rec)

O método update retorna um inteiro que indica o número de registros atualizados.



O método delete

Deletar registros da tabela é muito similar ao método update  usado para atualizar registros, exceto que você não deve passar uma lista contendo os nomes dos campos a serem atualizados e outra lista com os valores que atualizarão esses campos.


Como mencionado acima, para deletar um registro específico, simplesmente especifique na lista o campo 'recno' como sendo o campo a ser buscado. Então, para deletar o registro de recno 456 fazemos:

result =db.delete('plane.tbl', ['recno'],[456])

Para deletar múltiplos registros você deve especificar um critério de busca. O delete funciona da mesma maneira que o método select, só que ao invés de retornar os resultados, deleta-os. Portanto, para deletar todos aviões da Alemanha que tem o alcane menor que 800 milhas:

result =db.delete('plane.tbl', ['country', 'range'], ['Germany','<800'])

O método delete retorna um inteiro que indica o número de registros deletados.



O método pack

Quando o KirbyBase deleta um registro ele simplesmente substitui a linha do registro por espaços em branco. Deletar o registro e mover cada registro subsequente demandaria tempo demais. Por outro lado, quando um registro é atualizado, se os novos valores são maiores que os antigos, o KirbyBase apaga a linha do registro e o reescreve no final do arquivo. Desse modo nós não precisamos ficar reescrevendo o banco de dados a cada atualização de registro. Mas com o tempo o arquivo-tabela vai ficando com muitas linhas em branco ( que fazem as pesquisas demorarem mais ) e com o tamanho maior do que o desejado, aí usamos o método pack para limpar essas linhas em branco da tabela.
result = db.pack('plane.tbl')

O método pack retorna um inteiro indicando o número de linhas em branco removidas.



O método validate

KirbyBase sempre valida a consistência dos dados adicionados durante um método insert ou update, porém o que acontece se você adicionar os dados nas tabelas de outra maneira? por exemplo, uma das características mais interessantes do KirbyBase é que você pode simplesmente abrir o arquivo contendo a tabela e editá-lo no seu editor de textos preferido. Registros inseridos ou atualizados desta maneira provavelmente não estarão de acordo com os tipos dos campos associados quando criamos a tabela.

Obviamente, nós precisamos de uma maneira para validar se todos os dados contidos na tabela estão corretamente associados aos seus respectivos tipos de dados, definidos quando criamos a tabela. Este é o propósito do método validate. Para chamar o método basta:
result =db.validate('plane.tbl')

O método validate retorna uma lista contendo registros com valores errôneos. Cada registro é uma lista que contém o recno do registro, o nome do campo com valor inválido e o valor desse campo. Uma lista vazia indica que o banco de dados passou no teste.



O método drop

Esse método deleta uma tabela e inclusive todo o seu conteúdo. Exemplo:

result =db.drop('plane.tbl')

O método drop retorna True se a deleção ocorrer com sucesso.



O método addFields

Para adicionar novos campo para uma tabela, use este método:
db.addFields('plane.tbl', ['bomb_load:int'],after='range')

O exemplo acima adiciona um novo campo chamado "bomb_load" com o tipo int na tabela Plane. A variável after nos permite definir onde o KirbyBase criará o novo campo. Se você não definir a variável after, o novo campo será adicionado logo após o campo recno.


O método addFields retorna True se a criação do novo campo ocorrer com sucesso.



O método dropFields

Para remover um campo de uma tabela:

db.dropFields('plane.tbl', ['bomb_load:int'])

O exemplo acima remove um campo chamado bomb_load da tabela Plane. O método dropFields retorna True se a deleção ocorrer com sucesso.



Outros métodos

result =db.getFieldNames('plane.tbl')

Retorna uma lista contendo os nomes dos campos da tabela Plane.

result =db.getFieldTypes('plane.tbl')

Retorna uma lista contendo os tipos dos campos.

totalRecords =db.len('./db/plane.tbl')

Retorna um inteiro que indica o número total de registros na tabela.

db.setDefaultReturnType('object')

O tipo padrão de retorno para o método select é 'list', isto é, uma lista. Mas você pode alterá-lo na própria chamada do método definindo a função returnType, mas isso pode ficar trabalhoso caso você sempre queira um tipo diferente da lista. Esse método permite você definir um outro tipo padrão de retorno do método select.



Problemas: Caracteres especiais nos valores

Como as tabelas do KirbyBase são simples arquivos texto, a inserção de caracteres como "\n", "\r\n" e "|" podem causar problemas na leitura do banco de dados. Tendo em vista que "\n" ( unix ) e "\r\n" ( windows ) são o delimitadores entre registros e o "|" que delimita os valores e os campos.

Mas relaxe, o KirbyBase troca esses caracteres por codificações que os tornam "menos perigosos" :)
A seguir a tabela com os valores pelo qual cada caractere-perigoso é substituido:

Caracter de entrada Troca feita pelo KirbyBase
\n &linefeed;
\r &carriage_return;
\032 &substitute;
| &pipe;

Essa tradução de caracteres-perigosos feita pelo KirbyBase é totalmente trasparente para o usuário, e nada impede que você os use. Você só irá perceber essas transformações se abrir a tabela em algum editor de texto.


Estrutura da tabela

Cada tabela do KirbyBase é um arquivo texto simples delimitado por "newlines" ( quebras de linha ) :

000006|000000|recno:int|name:str|country:str|speed:int|range:int
1|P-51|USA|403|1201
2|P-51|USA|365|888
3|Sptitfire|England|345|540
4|Oscar|Japan|361|777
5|ME-109|Germany|366|514
6|Zero|Japan|377|912

A primeira linha é cabeçalho. Cada campo é delimitado por um "|". O primeiro campo no cabeçalho indica o número de registros. Ele é incrementado pelo KirbyBase automaticamente quando novos registros são inseridos. O segundo campo é o número de campos deletados. Toda vez que um registro é deletado, esse número é incrementado. Você pode usar esse campo para criar rotinas de manutenção, tipo, rodar o método pack sempre que esse valor chegar a 5000. O terceiro campo é o campo recno, ele é adicionado automaticamente quando a tabela é criada. Os outros campos são campos definidos pelo usuário quando este criou a tabela.


Cada registro na tabela é uma linha do texto. A quebra de linha delimita os registros.



Notas sobre servidores:

Existem dois scripts para servidores incluidos nessa distribuição:

kbsimpleserver.py - cria um server de thread única. Isto significa que esse servidor atende a requisições de clientes de modo sequencial. Se um cliente faz uma seleção de 10.000 registros, o usuário atrás dele na fila terá que aguardar, mesmo que este esteja querendo acessar outra tabela. Esse servidor é bom para pequenas tabelas que recebem pequenas e simples queries.

kbthreadedserver.py - cria um servidor com múltiplas threads. Isto significa que cada cliente conecta para uma thread própria no server. A única chance de uma thread "atrasar" outra é caso ela abra a tabela para modificação ( métodos: update, delete e insert ). Mesmo nesse caso, ela só bloqueará outras thread com requisições para alterar o arquivo também. Outra thread que queira apenas fazer um select não será bloqueada. Nesse servidor, um usuário só terá que esperar pelo termino da requisição de outro usuário caso os dois estejam usando a mesma tabela. Esse servidor consegue implementar isso graças a uma lista de "locks" para as tabelas.


***Observação: Ainda que eu tenha testado o kbthreadedserver.py algumas vezes, eu ainda o considero um beta. Faça seus próprios testes antes de pô-lo em produção.



Licença

KirbyBase está licenciado sobre a Python Software Foundation License.