Eigenbase is architected to encourage the creation of multiple distributions, each with its own system-level extensions and unique personality. The personality concept covers many aspects of session behavior, including parsing, validation, optimization, and execution. And SQL:2003 defines a taxonomy of features, not all of which are possible or desired in all Eigenbase extensions. For example, it may not be possible to efficiently implement the LONG VARCHAR dataype in a real-time DBMS.
Consequently, it is important for Eigenbase to provide the ability to selectively enable and disable different feature sets in different personalities without requiring multiple implementations for shared components such as validators. This page provides a walkthrough of the framework used to achieve this.
In SQL:2003, features are defined in Annex F of each part (standard relational features are defined in Part 2). Each feature has a coded ID (e.g. E051-01), a name (e.g. SELECT DISTINCT) and a description, including references to portions of the spec where the presence of the feature affects behavior.
We map these features into resource definitions in the source file farrago/src/org/eigenbase/resource/EigenbaseResource.xml. For an example, search for SQLFeature_E051_01 in that file. Note that underscores are used instead of dashes to make the ID codes into valid Java identifiers. We take advantage of ResGen's property support to annotate each feature with a property named FeatureDefinition whose value is a reference to the containing spec. TBD: naming conventions for extension features.
Like any other resource definition, the build converts these XML descriptors into attributes in class EigenbaseResource. This means that code that wants to make decisions based on enabled feature support can refer to a feature by its code, e.g. EigenbaseResource.instance().SQLFeature_E051_01. Such references are early-bound, meaning that if code refers to a non-existent feature, it is guaranteed to cause a build error.
Normally, it is the responsibility of the validator to detect usage of disabled features and report an error. Eigenbase currently has two primary validators: SqlValidator, which validates queries and DML statements; and DdlValidator, which validates everything else.
As an example of a feature enablement check, method SqlValidatorImpl.validateSelect contains a call to method validateFeature to test for support of the DISTINCT modifier. This call takes a reference to the feature (early-bound as discussed previously) and a reference to the DISTINCT parse node for error context in case the feature is disabled. validateFeature calls the current session personality to test the support for this feature, and if false is returned, an exception is thrown.
Given the number of features defined by SQL and various extensions, the number of feature checks in the validator will be large. How do we test that all of these checks are doing their jobs properly? Class SqlValidatorFeatureTest provides a framework. For each feature, we define a test method which contains:
- an SQL statement making use of the feature
- error markers (carets) within the statement delineating the location which should be flagged when the feature is disabled
- an early-bound reference to the feature
The test harness executes each statement twice. The first time, it executes with a validator which enables the feature; the test harness verifies that the validator succeeds. The second time, it executes with the feature disabled, and verifies that an exception is thrown (indicating the offending location if it can be pinpointed).
As mentioned previously, at runtime it is the responsibility of a session's personality to decide which features it supports. Interface FarragoSessionPersonality provides method supportsFeature for this purpose. It takes a feature reference and returns a boolean for whether it is enabled.
The implementation for this method can be hard-coded into a personality; for example, the personality constructor can create a HashSet and add references to all of the features it supports, then test this set's membership when supportsFeature is called.
Eventually, we may want to build up some utilities to allow for composition of feature sets and the ability to load them from resource files at runtime (with nothing hardcoded).
We use the RNG plugin example to test support for per-personality feature enablement. The RNG plugin implementation is instrumented to return false when supportsFeature is called for SELECT DISTINCT. (Yes, this is arbitrary and has nothing to do with random-number-generators.) Here's the sequence from farrago/examples/rng/unitsql/testPlugin.ref which verifies the behavioral changes:
0: jdbc:farrago:> -- enable plugin personality for this session 0: jdbc:farrago:> alter session implementation set jar sys_boot.sys_boot.rngplugin; 0: jdbc:farrago:> select distinct empno from sales.emps order by empno; Error: SELECT DISTINCT not supported (state=,code=0) 0: jdbc:farrago:> 0: jdbc:farrago:> -- now, disable plugin personality for this session 0: jdbc:farrago:> alter session implementation set default; 0: jdbc:farrago:> -- verify that SELECT DISTINCT is working again 0: jdbc:farrago:> select distinct empno from sales.emps order by empno;