-
Notifications
You must be signed in to change notification settings - Fork 42
Add support for universal sets and unions #421
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
06675f1
eed50f3
a26f72f
930fc6f
0987b3c
77dec54
208c789
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |||||
| properties(SetAccess=private) | ||||||
| info % table info | ||||||
| attributes % array of attributes | ||||||
| distinct=false% whether to select all the elements or only distinct ones | ||||||
| end | ||||||
|
|
||||||
| properties(Access=private,Constant) | ||||||
|
|
@@ -204,7 +205,8 @@ function project(self, params) | |||||
| else | ||||||
| % process a regular attribute | ||||||
| ix = find(strcmp(params{iAttr},self.names)); | ||||||
| assert(~isempty(ix), 'Attribute `%s` does not exist', ... | ||||||
| assert(~isempty(ix),'DataJoint:missingAttributes',... | ||||||
| 'Attribute `%s` does not exist', ... | ||||||
| params{iAttr}) | ||||||
| end | ||||||
| end | ||||||
|
|
@@ -242,7 +244,8 @@ function project(self, params) | |||||
| function sql = sql(self) | ||||||
| % make an SQL list of attributes for header | ||||||
| sql = ''; | ||||||
| assert(~isempty(self.attributes)) | ||||||
| assert(~isempty(self.attributes),... | ||||||
| 'DataJoint:missingAttributes','Relation has no attributes'); | ||||||
| for i = 1:length(self.attributes) | ||||||
| if isempty(self.attributes(i).alias) | ||||||
| % if strcmp(self.attributes(i).type,'float') | ||||||
|
|
@@ -266,7 +269,11 @@ function project(self, params) | |||||
| end | ||||||
| end | ||||||
| end | ||||||
| sql = sql(2:end); % strip leading comma | ||||||
| sql = sql(2:end); % strip leading comma | ||||||
|
|
||||||
| if self.distinct | ||||||
| sql = sprintf('DISTINCT %s', sql); | ||||||
| end | ||||||
| end | ||||||
|
|
||||||
|
|
||||||
|
|
@@ -276,4 +283,44 @@ function stripAliases(self) | |||||
| end | ||||||
| end | ||||||
| end | ||||||
|
|
||||||
| methods (Access = {?dj.internal.GeneralRelvar}) | ||||||
| function reorderFields(self, order) | ||||||
| assert(length(order) == length(self.names)); | ||||||
| self.attributes = self.attributes(order); | ||||||
| end | ||||||
|
|
||||||
| function promote(self, keep, varargin) | ||||||
| if ~keep | ||||||
| [self.attributes(:).iskey] = deal(false); | ||||||
| self.distinct = true; | ||||||
| self.project(varargin); % do the projection | ||||||
| else | ||||||
| self.project([varargin, '*']); | ||||||
| end | ||||||
|
|
||||||
| % promote the keys | ||||||
| for iAttr = 1:numel(varargin) | ||||||
| %renamed attribute | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| toks = regexp(varargin{iAttr}, ... | ||||||
| '^([a-z]\w*)\s*->\s*(\w+)', 'tokens'); | ||||||
| if ~isempty(toks) | ||||||
| name = toks{1}{2}; | ||||||
| else | ||||||
| %computed attribute | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| toks = regexp(varargin{iAttr}, '(.*\S)\s*->\s*(\w+)', 'tokens'); | ||||||
| if ~isempty(toks) | ||||||
| name = toks{1}{2}; | ||||||
| else | ||||||
| %regular attribute | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| name = varargin{iAttr}; | ||||||
| end | ||||||
| end | ||||||
| ix = find(strcmp(name, self.names)); | ||||||
| assert(~isempty(ix), 'DataJoint:missingAttributes', 'Attribute `%s` does not exist', ... | ||||||
| name) | ||||||
| self.attributes(ix).iskey = true; | ||||||
| end | ||||||
| end | ||||||
| end | ||||||
| end | ||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,57 @@ | ||||||
| classdef U | ||||||
| properties (SetAccess=private, Hidden) | ||||||
| primaryKey | ||||||
| end | ||||||
|
|
||||||
| methods | ||||||
| function self = U(varargin) | ||||||
| % UNIVERSAL SET - a set representing all possible values of the | ||||||
| % supplied attributes | ||||||
| % Can be queried in combination with other relations to alter their | ||||||
| % primary key structure. | ||||||
|
|
||||||
| self.primaryKey = varargin; | ||||||
| % self.init('U', {}); % general relvar node | ||||||
| end | ||||||
|
|
||||||
| function ret = and(self, arg) | ||||||
| ret = self.restrict(arg); | ||||||
| end | ||||||
|
|
||||||
| function ret = restrict(self, arg) | ||||||
| % RESTRICT - relational restriction | ||||||
| % dj.U(varargin) & A returns the unique combinations of the keys in | ||||||
| % varargin that appear in A. | ||||||
|
|
||||||
| % for dj.U(), only support restricting by a relvar | ||||||
| assert(isa(arg, 'dj.internal.GeneralRelvar'),... | ||||||
| 'restriction requires a relvar as operand'); | ||||||
|
|
||||||
| % self = init(dj.internal.GeneralRelvar, 'U', {self, arg, 0}); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please remove all instances commented code
Suggested change
|
||||||
| ret = init(dj.internal.GeneralRelvar, 'U', {arg, self, 0}); | ||||||
| end | ||||||
|
|
||||||
| function ret = mtimes(self, arg) | ||||||
| % MTIMES - relational natural join. | ||||||
| % dj.U(varargin) * A promotes the keys in varargin to the primary | ||||||
| % key of A and returns the resulting relation. | ||||||
|
|
||||||
| assert(isa(arg, 'dj.internal.GeneralRelvar'), ... | ||||||
| 'mtimes requires another relvar as operand') | ||||||
| ret = init(dj.internal.GeneralRelvar, 'U', {arg, self, 1}); | ||||||
| end | ||||||
|
|
||||||
| function ret = aggr(self, other, varargin) | ||||||
| % AGGR -- relational aggregation operator. | ||||||
| % dj.U(varargin).aggr(A,...) Allows grouping by arbitrary | ||||||
| % combinations of the keys in A. | ||||||
|
|
||||||
| assert(iscellstr(varargin), ... | ||||||
| 'proj() requires a list of strings as attribute args') | ||||||
| ret = init(dj.internal.GeneralRelvar, 'aggregate', ... | ||||||
| [{self & other, self * other}, varargin]); | ||||||
| %Note: join is required here to make projection semantics work. | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| end | ||||||
| end | ||||||
|
|
||||||
| end | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,5 +13,6 @@ | |
| TestSchema & ... | ||
| TestTls & ... | ||
| TestUuid & ... | ||
| TestBlob | ||
| TestBlob & ... | ||
| TestRelationalOperator | ||
| end | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,92 @@ | ||||||
| classdef TestRelationalOperator < Prep | ||||||
| methods (TestClassSetup) | ||||||
| function init(testCase) | ||||||
| init@Prep(testCase); | ||||||
| package = 'University'; | ||||||
| dj.createSchema(package,[testCase.test_root '/test_schemas'], ... | ||||||
| [testCase.PREFIX '_university']); | ||||||
| University.Student().insert(struct(... | ||||||
| 'student_id', {1,2,3,4},... | ||||||
| 'first_name', {'John','Paul','George','Ringo'},... | ||||||
| 'last_name',{'Lennon','McCartney','Harrison','Starr'},... | ||||||
| 'enrolled',{'1960-01-01','1960-01-01','1960-01-01','1960-01-01'}... | ||||||
| )); | ||||||
|
|
||||||
| University.A().insert({1, 'test', '1960-01-01 00:00:00','1960-01-01', 1.234, struct()}); | ||||||
| end | ||||||
|
|
||||||
| end | ||||||
| methods (Test) | ||||||
| function TestRelationalOperator_testUnion(testCase) | ||||||
| st = dbstack; | ||||||
| disp(['---------------' st(1).name '---------------']); | ||||||
|
|
||||||
|
|
||||||
| % Unions may only share primary, but not secondary, keys | ||||||
| testCase.verifyError(@() count(University.Student() | University.Student()), 'DataJoint:invalidUnion'); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Switch |
||||||
| % The primary key of each relation must be the same | ||||||
| testCase.verifyError(@() count(University.A() | University.Student()), 'DataJoint:invalidUnion'); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| % A basic union | ||||||
| testCase.verifyEqual(count(... | ||||||
| proj(University.Student() & 'student_id<2') | proj(University.Student() & 'student_id>3')),... | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| 2); | ||||||
|
|
||||||
| % Unions with overlapping primary keys are merged | ||||||
| testCase.verifyEqual(count(... | ||||||
| proj(University.Student() & 'student_id<3') | proj(University.Student() & 'student_id>1 AND student_id<4')),... | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| 3); | ||||||
|
|
||||||
| % Unions with disjoint secondary keys are also merged and filled with NULL | ||||||
| a = University.Student & 'student_id<4'; | ||||||
| b = proj(University.Student() & 'student_id>1','"test_val"->test_col'); | ||||||
| c = fetch(a | b, '*'); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| testCase.verifyEqual(length(c), 4); | ||||||
| testCase.verifyEqual(nnz(cellfun(@isempty,{c(:).first_name})), 1); | ||||||
| testCase.verifyEqual(nnz(cellfun(@isempty,{c(:).test_col})), 1); | ||||||
| testCase.verifyEqual(nnz(cellfun(@isempty,{c(:).first_name}) & cellfun(@isempty,{c(:).test_col})), 0); | ||||||
|
|
||||||
| end | ||||||
|
|
||||||
| function TestRelationalOperator_testUniversalSet(testCase) | ||||||
| st = dbstack; | ||||||
| disp(['---------------' st(1).name '---------------']); | ||||||
|
|
||||||
| % dj.U() & rel has no attributes | ||||||
| a = dj.U() & University.Student(); | ||||||
| testCase.verifyError(@() a.header.sql, 'DataJoint:missingAttributes'); | ||||||
|
|
||||||
| % dj.U(c) * rel is invalid if c is not an attribute of rel | ||||||
| testCase.verifyError(@() count(dj.U('bad_attribute') * University.Student()), 'DataJoint:missingAttributes'); | ||||||
|
|
||||||
| % rel = dj.U(c) * rel promotes c to a primary key of rel | ||||||
| a = dj.U('first_name') * University.Student(); | ||||||
| testCase.verifyTrue(ismember('first_name', a.primaryKey)); | ||||||
| testCase.verifyEqual(length(a.primaryKey), 2); | ||||||
|
|
||||||
| % dj.U(c) & rel returns the unique combinations of c in rel | ||||||
| a = dj.U('enrolled') & University.Student(); | ||||||
| testCase.verifyEqual(count(a), 1); | ||||||
| testCase.verifyEqual(length(a.header.attributes), 1); | ||||||
| a = dj.U('last_name','enrolled') & University.Student(); | ||||||
| testCase.verifyEqual(count(a), 4); | ||||||
| testCase.verifyEqual(length(a.header.attributes), 2); | ||||||
|
|
||||||
| % dj.U(c).aggr(rel, ...) aggregates into the groupings in c that exist in rel | ||||||
| a = dj.U('last_name').aggr(University.Student(), 'length(min(first_name))->n_chars'); | ||||||
| testCase.verifyEqual(length(a.primaryKey),1); | ||||||
| testCase.verifyTrue(strcmp(a.primaryKey{1}, 'last_name')); | ||||||
| testCase.verifyEqual(count(a), 4); | ||||||
| testCase.verifyEqual(length(a.nonKeyFields), 1); | ||||||
| testCase.verifyTrue(strcmp(a.nonKeyFields{1}, 'n_chars')); | ||||||
|
|
||||||
| % dj.U(c) supports projection semantics on c | ||||||
| a = dj.U('left(first_name,1)->first_initial') & University.Student(); | ||||||
| testCase.verifyEqual(length(a.primaryKey), 1); | ||||||
| testCase.verifyTrue(strcmp(a.primaryKey{1}, 'first_initial')); | ||||||
| testCase.verifyEqual(count(a), 4); | ||||||
|
|
||||||
|
|
||||||
| end | ||||||
| end | ||||||
| end | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.