Neste artigo vamos construir esquemas de banco de dados para o banco Matisse pela linguagem ODL. Serão descritos os conceitos do Paradigma de Orientação a Objetos que são aplicados nessa linguagem.
Para criar um esquema em Matisse ODL deve-se criar um arquivo de texto com a extensão “.odl” contendo um conjunto de declarações. Você poderá então usar o utilitário Matisse Enterprise Manager para carregar o esquema em um banco de dados recém-inicializado e gerar o código-fonte para uso em linguagens como C++, Java, PHP, Python ou Eiffel.
Definindo Namespaces
Os Namespaces são utilizados em linguagens de programação para uma melhor organização das classes. Uma boa forma de programação para melhor a organização da arquitetura de um sistema de software. No caso dos bancos de dados orientados a objetos, trata-se também em uma forma de tentar alinhar a arquitetura do banco de dados às necessidades arquiteturais de um sistema de software. No código abaixo temos a declaração de um Namespace:
module nome_do_namespace {...};
Dentro de um Namespace poderão ser inseridos declarações de classes e demais componentes da notação ODL. Também podemos realizar uma hierarquia de Namescpace, com declaração de classes independente para cada um, como mostra o exemplo da Listagem 1.
Listagem 1. Exemplo de declaração de hierarquia de Namespace.
module MyCompany
{
module MyApp
{
interface Person : persistent
{
attribute String<16> name;
};
interface Company : persistent
{
attribute String<32> name;
};
module HR
{
interface Employee: MyCompany.MyApp.Person: persistent
{
};
interface Manager: Employee: persistent
{
};
};
};
};
Declaração de Classes
UmaClasseé um elemento que abstrai um conjunto de objetos com características similares. Classes são utilizadas para definir comportamentos de seus objetos pelo uso de métodos e os estados possíveis destes objetos através de seus atributos. Ou seja, classes servem para descrever os serviços ofertados pelos seus objetos e quais informações eles armazenam, como mostra o código a seguir:
interface nome_da_classe: persistent {...};
As declarações permitidas dentro do parêntese são atributos associados, relacionamentos, índices e dicionários de pontos de entrada, separados por ponto e vírgula. A linguagem ODL também suporta mecanismos de herança entre classes, como mostra o código a seguir, onde as classes herdades herdam todas as características da classe pai:
interface nome_da_classe: nome_da_super_classe: persistent {...};
Para utilizar o mecanismo de herança múltipla, basta enumerar as superclasses separadas por uma vírgula, como mostrado a seguir:
interface nome_da_classe: nome_da_super_classe1, nome_da_super_classe2: persistent {...};
Declarando atributos
Os Atributos correspondem as características ou estados de objetos cuja definição é feita no momento da declaração de uma classe, como abaixo:
attribute tipo_do_atributo nome_do_atributo;
A Tabela 1 a seguir ilustra os diversos tipos de atributos suportados pela linguagem Matisse ODL, que também suporta a declaração de lista de atributos do mesmo tipo, como mostra a Listagem 2 e também de atributos nulos, como mostra o código abaixo, cano da declaração de um atributo não seja especificado um valor este atributo será declarado com valor nulo.
attribute tipo_do_atributo Nullable nome_do_atributo;
Tipo |
Descrição |
Boolean |
Valores entre True ou false |
Byte |
Um byte único. Com valores na faixa de 0 à 255. |
Char |
Um único caractere de byte. |
Date |
Os valores de Date são Year, month, day. |
Double |
Um número ponto flutuante de dupla precisão, aproximadamente um número real de 64-bit. |
Float |
Um número ponto flutuante de precisão única, aproximadamente um número real de 32-bit. |
Integer. |
Um número inteiro de 4 bytes. |
Interval |
Um período de tempo em dias, horas, minutos, segundos e microssegundos. |
Long |
Número inteiro de 8 bytes. |
Numeric |
Valor numérico genérico |
Numeric(p, s) |
Valor numérico genérico em que se informa o número de casas decimais a esquerda e a direita da vírgula. |
Short |
Número inteiro de 2 bytes. |
String |
String sem limitação de caracteres. Tamanho máximo de uma String de até 2147483648 bits (2Gbytes). |
String |
String com limitação de caracteres. Tamanho máximo de uma String de até 2147483648 bits (2Gbytes). |
String UTF16 |
String sem limitação de caracteres. Tamanho máximo de até 1073741824 (1Gb). |
String UTF16 |
String com limitação de caracteres. Tamanho máximo de até 1073741824 (1Gb). |
Timestamp |
Data e tempo consistindo de ano, mês, dia, hora, minutos, segundos e microssegundos. |
Audio Audio Bytes Bytes Image Image Video Video |
Trata-se de objeto de stream de dados binário. Opcionalmente com um número de bytes n para fixar tamanho máximo. O tamanho máximo de um destes tipos é 2147483648. Se valor máximo não for especificado o tamanho máximo é 2147483648. |
Text Text |
Trata-se de um objeto de stream de caracteres. Opcionalmente com um número de bytes n para fixar tamanho máximo. O tamanho máximo de um destes tipos é 2147483648. Se valor máximo não for especificado o tamanho máximo é 2147483648. |
Text UTF16 |
Stream de caracteres Unicode. Opcionalmente com o número máximo de caracteres n. O número máximo de caracteres deste tipo é 1073741824. Se n não for especificado o tamanho máximo será 1073741824. |
Text UTF16 |
Tabela 1. Descrição dos principais tipos de atributos suportados pela linguagem ODL.
Listagem 2. Declaração de atributos em lista.
attribute List<tipo_do_atributo> nome_do_atributo;
attribute List<tipo_do_atributo, número_máximo_de_elementos> nome_do_atributo;
A Listagem 3 ilustra os tipos de listas de atributos suportados pelo Matisse ODL.
Listagem 3. Descrição dos tipos de lista de atributos suportados pela ODL.
List<Boolean>
List<Date>
List<Double>
List<Float>
List<Integer>
List<Interval>
List<Long>
List<Numeric>
List<Short>
List<String>
List<String UTF16>
List<Timestamp>
Para a declaração de atributos com valores de início basta seguir o modelo abaixo para atributos com valores válidos:
attribute tipo_do_atributo nome_do_atributo = valor_padrão;
E para valores nulos use o modelo abaixo:
attribute tipo_do_atributo nullable nome_do_atributo = NULL;
Também é possível a inicialização de lista de atributos com valores válidos ou nulos, como mostram os códigos a seguir:
attribute List<tipo_do_atributo> nome_do_atributo = List<tipo_do_atributo>{ valor_padrao1, valor_padrao2, ...};
attribute List<tipo_do_atributo> nome_do_atributo = List<tipo_do_atributo>{};
Tipo de Atributo |
Forma de inicialização |
Boolean |
TRUE|FALSE |
Byte |
Byte(valor) |
Char |
'a' |
Date |
Date("YYYY-MM-DD") |
Double |
Double(Valor) |
Float |
Float(Valor) |
Integer |
Valor |
Interval |
Interval("[-]days HH:MM:SS.MMMMMM"). Note que existe um espaço após o valor dos dias. |
Long |
Long(Valor) |
Numeric |
Numeric(0.00) (O único valor padrão permitido é 0.) |
Short |
Short(Valor) |
String |
"string" |
String UTF16 |
UTF16("cycling") UTF16("v\u00E9lo") UTF16("\u81EA\u884C\u8F66") |
Timestamp |
Timestamp("YYYY-MM-DD HH:MM:SS.MMMMMM") |
Audio |
Audio() |
Bytes |
Bytes Bytes() |
Image |
Image Image() |
Text |
Text("string") |
Text UTF16 |
Text UTF16("cycling") |
|
Text UTF16("v\u00E9lo") |
|
Text UTF16("\u81EA\u884C\u8F66") |
Video |
Video() |
Tabela 2. Sintaxe utilizada para inicialização de atributos.
Na Tabela 2 mostra a sintaxe utilizada para a inicialização de atributos.
Na Listagem 4 temos os exemplos de inicialização de atributos e na Listagem 5 os exemplos de listas de atributos.
Listagem 4. Exemplos de inicialização de atributos.
attribute Boolean Attibute_name= TRUE;
attribute Byte Attibute_name= Byte(0);
attribute Char Attibute_name= 'A';
attribute Date Attibute_name= Date("1997-07-29");
attribute Double Attibute_name= Double(0.000000);
attribute Float Attibute_name= Float(0.000000);
attribute Integer Attibute_name= 0;
attribute Interval Attibute_name= Interval("-29 12:00:00");
attribute Long Attibute_name= Long(0);
attribute Numeric Attibute_name= Numeric(0);
attribute Short Attibute_name= Short(0);
attribute String<32> Attibute_name= "ascii string";
attribute String<32> UTF16 Attibute_name= UTF16("v\u00E9lo");
attribute Text Attibute_name= Text("ascii string");
attribute Text UTF16 Attibute_name= Text
UTF16("\u81EA\u884C\u8F66");
attribute Timestamp Attibute_name= Timestamp("2000-12-31
12:00:00");
Listagem 5. Exemplos de inicialização de listas de atributos.
attribute List<Boolean> Attibute_name= List<Boolean>{ TRUE, FALSE
};
attribute List<Byte> Attibute_name= List<Byte>{ 11, 21 };
attribute List<Double> Attibute_name= List<Double>{ 11.100000,
21.100000 };
attribute List<Float> Attibute_name= List<Float>{ 11.100000,
21.100000 };
attribute List<Integer> Attibute_name= List<Integer>{ 11, 21 };
attribute List<Interval> Attibute_name= List<Interval>{
Interval("-19 12:00:00"), Interval("-29 12:00:00") };
attribute List<Long> Attibute_name= List<Long>{ 11, 21 };
attribute List<Numeric>{ Numeric(0.00, 0.00)};
attribute List<Short> Attibute_name= List<Short>{ 11, 21 };
attribute List<String> Attibute_name= List<String>{ "a","b","c" };
attribute List<String UTF16> Attibute_name= List<String>{
UTF16("cycling"), UTF16("v\u00E9lo"),
UTF16("\u81EA\u884C\u8F66") };
attribute List<Timestamp> Attibute_name= List<Timestamp>{
Timestamp("2000-12-31 12:00:00"), Timestamp("2001-01-01
12:00:00") };
Declaração de Relacionamentos
Existem três formas de se declarar um relacionamento:
1. Relacionamento de uma classe para outra;
2. Relacionamento entre uma lista de classes;
3. Relacionamentos entre um conjunto de classes.
Na Listagem 6 temos o código para cada um dos relacionamentos. O usuário também pode definir um relacionamento inverso para todos os três tipos de declarações. Entende-se como relacionamento inverso aquele em que uma classe possui uma nomenclatura na outra direção. Todo relacionamento possui um nome do ponto de partida da seta de relacionamento e um outro nome no ponto de chegada. Isto também existe na declaração de relacionamentos quando na modelagem de Banco de Dados Entidade-Relacionamento. O inverso da relação consiste, justamente, em nomear a seta no ponto de chegada. A Listagem 7 ilustra a declaração de um relacionamento com a sua nomenclatura inversa.
Listagem 6. Formas de declaração de relacionamentos com nome direto.
relationship classe_sucessora nome_direto_do_relacionamento;
relationship List <classe_sucessora> nome_direto_do_relacionamento;
relationship Set <classe_sucessora> nome_direto_do_relacionamento;
Listagem 7. Formas de declaração de relacionamentos com nome direto e inverso.
relationship classe_sucessora nome_direto_do_relacionamento
inverse classe_sucessora:: nome_inverso_do_relacionamento;
relationship List <classe_sucessora> nome_direto_do_relacionamento
inverse classe_sucessora:: nome_inverso_do_relacionamento;
relationship Set <classe_sucessora> nome_direto_do_relacionamento
inverse classe_sucessora:: nome_inverso_do_relacionamento;
Quando o usuário declara um relacionamento com List ou Set, a cardinalidade padrão será [0, -1] (qualquer número de sucessores). Sem o uso de List ou Set o padrão da cardinalidade será [1, 1] (um sucessor). Se o usuário definir a relação com nomenclatura direta e inversa, as relações devem ser declaradas em cada das duas classes envolvidas, como mostrada a Listagem 8.
Listagem 8. Exemplos uso de relacionamentos com nome direto e inverso.
interface Employee: persistent {
relationship Department MemberOf inverse Department::Members;
};
interface Department: persistent {
relationship List <Person> Members inverse Person::MemberOf;
};
Na declaração de relacionamentos também é possível fazer a declaração da cardinalidade dos relacionamentos, como a seguir:
relationship ... nome_direto_do_relacionamento [cardinalidade_mínima, cardinalidade_máxima] …
A Cardinalidade é especificada na parte direta relacionamento e para a cardinalidade explicitamente definida para ambas as extremidades de um relacionamento deve ser especificada para cada uma das declarações diretas. Quando List ou Set é usado, a cardinalidade se não for definida explicitamente será [0, -1] (mínimo zero, máximo ilimitado), onde qualquer número de sucessores, ou nenhum, podem ser especificados.
A Listagem 9 ilustra um exemplo da declaração de dois relacionamentos com uso de List que são equivalentes e a Listagem 10 ilustra mais exemplos de declarações de relacionamentos usando List.
Listagem 9. Declaração de relacionamentos equivalentes.
Relationship List<Person> Members
relationship List<Person> Members [0, -1]
Listagem 10. Exemplos de declarações de relacionamentos usando List.
Relationship List ... nome_direto_do_relacionamento [0, 2] ...
relationship List ... nome_direto_do_relacionamento [3, 10] ...
relationship List ... nome_direto_do_relacionamento [2, -1] ...
Sem o uso de List ou Set, a cardinalidade não sendo definida explicitamente será [1, 1]: um e somente um sucessor deve ser especificado. Os exemplos de relacionamentos ilustrados na Listagem 11 são equivalentes. A única outra cardinalidade apoiado sem o uso de List ou Set é [0, 1]: um sucessor único, ou nenhum, podem ser especificados. Sempre que a cardinalidade mínima é maior que 0, a relação deve sempre ter um sucessor. Neste caso, qualquer transação que cria um objeto ou limpa todos os sucessores deve definir um sucessor antes que possa ser cometido. Quando o usuário declara uma relação com um List, a ordem dos sucessores da relação é preservada. Por outro lado, se declarar um relacionamento com Set, a ordem das classes sucessoras não precisa ser preservada.
Listagem 11. Declaração de relacionamentos equivalentes.
relationship Department MemberOf inverse ...
relationship Department MemberOf [1, 1] inverse ...
Existe uma forma declarativa para relacionamentos chamada de somente leitura (readonly), como mostra o código a seguir:
readonly relationship ...
Quando a cláusula readonly é especificada, o relacionamento direto pode ser utilizado para obter ou consultar sucessores, mas não pode ser utilizada para atualizar ou apagar informações.
Um relacionamento pode ter mais de uma classe sucessora: por exemplo, uma classe de nome Person pode ter um relacionamento Owner/OwnedBy com as classes Car e House. Normalmente, uma forma mais simples e flexível para representação como tal do mundo real é pelo mecanismo de herança. Por exemplo, a classe Person tem um relacionamento Owner/OwnedBy com a classe Property, e que teria subclasses Car e House. Note que este tipo de relação não é uma relação "n-ária", isto é, ele não pode ser usado para criar ligações entre objetos Successor_class_1 e objetos Successor_class_2.
Veja um exemplo de declaração no exemplo a seguir:
relationship List <classe_sucessora_1, classe_sucessora_2>
nome_direto_do_relacionamento inverse nome_inverso_do_relacionamento;
Declaração de Indexadores
A linguagem ODL também dá suporte a indexadores. Indexadores são utilizados como índices para consulta de objetos armazenados nos esquemas de banco de dados. A linguagem ODL contém dois tipos de indexadores, critério de índice e chave única. Um índice pode incluir até quatro dos atributos de sua classe (que pode ser herdada de superclasses ao invés de declarados na classe), que são referidos como criteria for index, como mostra aListagem 12. Acláusula sort_order suporta somente dois valores: MT_ASCEND, para valores crescentes ou MT_DESCEND, para valores decrescentes. O tamanho máximo permitido para uma chave é de 256 bytes. Assim, a soma dos tamanhos para os atributos que estão indexados não pode exceder 256 bytes.
Listagem 12. Declaração de índices.
mt_index nome_do_indice
{nome_da_classe::nome_do_atributo1 sort_order},
{nome_da_classe::nome_do_atributo2 sort_order},
{nome_da_classe::nome_do_atributo3 sort_order},
{nome_da_classe::nome_do_atributo4 sort_order};
O outro tipo de indexador é a chave única, como mostra a Listagem 13. Se for definido como TRUE, cada entrada no índice deve ser único, permitindo que eles sejam utilizados como chaves primárias. O padrão de declaração de chave única é FALSE, por isso, se essa opção não for especificada o índice pode conter entradas duplicadas. A cláusula unique_key deve ser especificada antes de critérios.
Listagem 13. Declaração de chave única.
mt_index nome_do_indice
unique_key TRUE
criteria {nome_da_classe::nome_do_atributo sort_order}...
Declaração de Dicionários de Ponto de Entrada
Outra característica do ODL são os dicionários de ponto de entrada (Listagem 14), também conhecidos como dicionários de dados. Estes dicionários permitem que os analistas obtenham informações sobre todos os objetos do modelo de forma textual, contendo explicações por vezes difíceis de incluir no diagrama. Estes dicionários só suportam um tipo de atributo de dados, o tipo String. Dicionários no ODL também suportam o uso de chave únicas (Listagem 15). Se for definido como TRUE, cada entrada no dicionário deve ser único, permitindo que eles sejam utilizados como chaves primárias. O padrão é FALSE, por isso, se essa opção não for especificado o dicionário pode conter entradas duplicadas.
Listagem 14. Declaração de dicionário.
mt_entry_point_dictionary nome_do_dicionário
entry_point_of nome_do_atributo;
Listagem 15. Declaração de dicionário com chave única.
mt_entry_point_dictionary nome_do_dicionário
entry_point_of nome_do_atributo unique_key TRUE
Os Dicionários possuem modo de pesquisa case-sensitive (Listagem 16). Se for definido como TRUE, pesquisas no dicionário serão case-sensitive, caso contrário, elas são maiúsculas e minúsculas. O padrão é FALSE, por isso as pesquisas sendo essa opção não especificada serão case-insensitive.
Listagem 16. Declaração de dicionário com a cláusula case-sensitive.
mt_entry_point_dictionary nome_do_dicionário
entry_point_of nome_do_atributo case_sensitive TRUE
Além disso, também se faz necessário gerar os dicionários de dados:function_name especifica a função a ser usada para gerar o dicionário, assim como mostra a Listagem 17. Existem dois tipos de funções:
- "make-entry"(entrada padrão): gera um ponto de entrada por objeto com base no valor do atributo. Quando a função não é especificada, esta função é utilizada como padrão;
- "make-full-text-entry": gera um ponto de entrada separado para cada palavra na cadeia.
Listagem 17. Declaração de dicionário e sua função de entrada.
mt_entry_point_dictionary nome_do_dicionário
entry_point_of nome_do_atributo make_entry_function "nome_da_função"
Declaração de Métodos
A linguagem ODL permite a criação de métodos em classes e a partir deste ponto se faz uso da linguagem MATISSE DDL. AListagem 18 ilustra a declaração genérica de um método e aListagem 19 ilustra um exemplo.
OSQL_DDL_Statement é uma instrução para CREATE METHOD ou uma instrução CREATE STATIC METHOD. Isto gera um método em SQL no banco de dados.
Listagem 18. Declaração de método.
[mt_method
"SQL_DDL_statement"
Listagem 19. Exemplo do uso de método.
interface Person : persistent {
attribute String firstName;
attribute String lastName;
mt_method "CREATE METHOD fullName()
RETURNS STRING
FOR \”Person\”
BEGIN
RETURN CONCAT(firstName, lastName);
END;
";
};
O objetivo deste post foi abordar conceitos básicos sobre a linguagem ODL, para modelagem de banco de dados via script. Pode-se perceber que a linguagem é rica de parâmetros e de fácil uso, sem nomenclaturas difíceis de entender. O banco de dados Matisse conta com editor de suporte à edição de scripts ODL, este editor está integrado ao aplicativo Matisse Database Enterprise Manager. Espero que tenham gostado do post e até a próxima.