Skip to content

Heterogeneous and type-safe table structure

License

Notifications You must be signed in to change notification settings

echebbi/datatable

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

72 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Heterogeneous and type-safe tables

build status codecov

Motivation

This project is aimed to experiment with the implementation of heterogeneous yet type-safe table structure.

As of now, the API makes able to:

  • create heterogeneous tables,
  • modify them and their content,
  • query their content in a type-safe way.

Full example

Because code is better than words, here is a quick overview of the API:

ColumnId<Integer> AGE = id("age", Integer.class);
ColumnId<String> NAME = id("name", String.class);

Table people = new DataTable();
people.columns()
    .create(NAME, "Luc", "Baptiste", "Marie")
    .create(AGE, 23, 32, 42);
	
Table adultsWhoseNameEndsWithLetterE = people.filter(row ->
    row.get(AGE) > 18 &&
    row.get(NAME).startsWith("E")
);

Quick start

Understanding ids

Ids can be seen as references, as keys used to identify specific columns within a table. They are tricks used to provide a type-safe way to access the content of a table.

Basically, they are simple data structure containing:

  • a type, that is the Java class of the column's elements
  • a header, that is the name of the column.

An id is represented by the ColumnId class and can be created with the id static method :

// name is an id that matches any column containing Strings and which header is "col1"
ColumnId<String> name = ColumnId.id("col1", String.class);

In the following document, ids are used everywhere. However, although they are the advised way to deal with a table, they can always be replaced by indexes or column names.

Populating a Table

Creation of a new Table consists of an easy one-liner :

Table people = new DataTable();

A table can be filled either by adding new columns :

people.columns()
    .create("Name", String.class, "Luc", "Baptiste", "Marie")
    .create("Age", Integer.class, 23, 32, 42);

or by adding new rows :

people.rows()
    .create("Julie", 12)
    .create("Mathieu", 67);

The table resulting of the above statements is the following :

+----------+-----+
|   Name   | Age |
+----------+-----|
|   Luc    | 23  |
| Baptiste | 32  |
|   Marie  | 42  |
|   Julie  | 12  |
|  Mathieu | 67  |
+----------+-----+

Depopulating a Table

A table can be emptied either by removing columns :

people.columns()
    .remove("age");

or by removing rows :

people.rows()
   .remove(4)
   .remove(2);

Notice how the last row has been removed first in order to avoid bugs related to changes of ids. The table now looks like :

+----------+
|   Name   |
+----------+
|   Luc    |
| Baptiste |
|   Julie  |
+----------+

Querying a Table

The filter method makes easy to retrieve the rows of a table that match a specific criterion:

import static fr.kazejiyu.generic.datatable.core.impl.ColumnId.*;

public class Main {

    // Reference table's columns
    private static final ColumnId<Integer> AGE = id("age", Integer.class);
    private static final ColumnId<String> NAME = id("name", String.class);
    
    Table adultsWhoseNameEndsWithLetterE(Table people) {
        return people.filter(row ->
            row.get(AGE) > 18 &&
            row.get(NAME).startsWith("E")
        );
    }
}

SQL-like DSL

For more complex cases, the API also brings a SQL-like DSL that makes possible to apply a same filter to multiple columns. It can be used as follows :

import static fr.kazejiyu.generic.datatable.core.impl.ColumnId.*;

public class Main {
	
    // Reference table's columns
    private static final ColumnId<Integer> AGE = id("age", Integer.class);
    private static final ColumnId<String> NAME = id("name", String.class);
    private static final ColumnId<String> SURENAME = id("surename", String.class);

    // Retrieve all european adults whose both name and surename ends with the letter "e"
    Table adultsWhoseNameEndsWithLetterE(Table people) {
        return Query
            .from(people)
            .where(s(NAME, SURENAME)).endsWith("e")
            .and(AGE).gt(18)
            .and(COUNTRY).match(Country::isInEurope)
            .select();
    }
}

Notice how the s method is used to specify that the columns concerning by the WHERE clause contain String instances. That makes possible to use tailored methods (such as endsWith in the above example) ; because of Java's type erasure, this trick is mandatory. This static method is defined in the ColumnId class, as well as the methods:

  • n: used to apply a same filter to several columns of numbers,
  • b: used to apply a same filter to several columns of booleans.