diff --git a/api-src/org/labkey/api/snd/SNDDomainKind.java b/api-src/org/labkey/api/snd/SNDDomainKind.java
index d5c46d9..d7c57a0 100644
--- a/api-src/org/labkey/api/snd/SNDDomainKind.java
+++ b/api-src/org/labkey/api/snd/SNDDomainKind.java
@@ -15,12 +15,17 @@
*/
package org.labkey.api.snd;
+import org.labkey.api.data.CompareType;
import org.labkey.api.data.Container;
+import org.labkey.api.data.CoreSchema;
+import org.labkey.api.data.SQLFragment;
+import org.labkey.api.data.dialect.SqlDialect;
import org.labkey.api.exp.property.Domain;
import org.labkey.api.gwt.client.model.GWTPropertyDescriptor;
import org.labkey.api.query.ExtendedTableDomainKind;
import org.labkey.api.security.User;
import org.labkey.api.security.permissions.AdminPermission;
+import org.labkey.api.settings.AppProps;
import org.labkey.data.xml.domainTemplate.DomainTemplateType;
import org.labkey.data.xml.domainTemplate.SNDTemplateType;
@@ -89,4 +94,23 @@ public boolean matchesTemplateXML(String templateName, DomainTemplateType templa
{
return template instanceof SNDTemplateType;
}
+
+
+ public static String formatSndDomainURI(int containerRowId, int packageId)
+ {
+ return String.format("urn:lsid:%s:package-snd.Folder-%d:Package-%d",
+ AppProps.getInstance().getDefaultLsidAuthority(),
+ containerRowId,
+ packageId);
+ }
+
+ public static SQLFragment likeSndDomainURI(Integer containerRowId, Integer packageId)
+ {
+ String pattern = String.format("urn:lsid:%s:package-snd.Folder-%s:Package-%s",
+ CompareType.escapeLikePattern(AppProps.getInstance().getDefaultLsidAuthority(), '!'),
+ null==containerRowId ? "%" : Integer.toString(containerRowId),
+ null==packageId ? "%" : Integer.toString(packageId)
+ );
+ return new SQLFragment("LIKE ").appendValue(pattern).append(" ESCAPE '!'");
+ }
}
diff --git a/resources/schemas/snd.xml b/resources/schemas/snd.xml
index e8628bb..ea90e8f 100644
--- a/resources/schemas/snd.xml
+++ b/resources/schemas/snd.xml
@@ -236,12 +236,24 @@
-
-
+
+
+ NOLOOKUP
+
+
+
+
+ NOLOOKUP
+
+
-
-
+
+ true
+
+
+ true
+
lsidtype
true
diff --git a/src/org/labkey/snd/CategoryUserSchema.java b/src/org/labkey/snd/CategoryUserSchema.java
new file mode 100644
index 0000000..f164883
--- /dev/null
+++ b/src/org/labkey/snd/CategoryUserSchema.java
@@ -0,0 +1,76 @@
+package org.labkey.snd;
+
+import org.jetbrains.annotations.Nullable;
+import org.labkey.api.collections.CaseInsensitiveHashSet;
+import org.labkey.api.collections.CaseInsensitiveTreeSet;
+import org.labkey.api.data.ContainerFilter;
+import org.labkey.api.data.SQLFragment;
+import org.labkey.api.data.SqlSelector;
+import org.labkey.api.data.TableInfo;
+import org.labkey.api.query.QuerySchema;
+import org.labkey.api.query.SchemaKey;
+import org.labkey.api.query.UserSchema;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class CategoryUserSchema extends UserSchema
+{
+ public static final String SCHEMA_NAME = "Categories";
+
+ final SNDUserSchema _sndSchema;
+ final QuerySchema _packageSchema;
+
+ public CategoryUserSchema(SNDUserSchema parent)
+ {
+ super(new SchemaKey(parent.getSchemaPath(), SCHEMA_NAME), null, parent.getUser(), parent.getContainer(), parent.getDbSchema(), null);
+ _sndSchema = parent;
+ _packageSchema = _sndSchema.getSchema(PackageUserSchema.SCHEMA_NAME);
+ }
+
+ @Override
+ public Set getSchemaNames()
+ {
+ return Set.of();
+ }
+
+ @Override
+ public Set getTableNames()
+ {
+ var sql = new SQLFragment("SELECT Description FROM snd.PkgCategories WHERE Active=1 AND Container=").appendValue(getContainer());
+ List list = new SqlSelector(getDbSchema(), sql).getArrayList(String.class);
+
+ // check for duplicates
+ Set duplicates = new CaseInsensitiveHashSet();
+ Set ret = new CaseInsensitiveTreeSet();
+ for (String s : list)
+ if (!ret.add(s))
+ duplicates.add(s);
+ ret.removeAll(duplicates);
+
+ return ret;
+ }
+
+ @Override
+ public @Nullable TableInfo createTable(String name, ContainerFilter cf)
+ {
+ var sql = new SQLFragment("SELECT CategoryId, Description FROM snd.PkgCategories WHERE Active=1 AND Container=").appendValue(getContainer())
+ .append(" AND lower(description) = lower(").appendValue(name).append(")");
+ Map[] rs = new SqlSelector(getDbSchema(), sql).getMapArray();
+ if (rs.length != 1)
+ return null;
+ int categoryId = (Integer)rs[0].get("CategoryId");
+ name = (String)rs[0].get("Description");
+
+ PackageUserSchema packageUserSchema = (PackageUserSchema)_sndSchema.getUserSchema(PackageUserSchema.SCHEMA_NAME);
+ if (null == packageUserSchema)
+ return null;
+
+ /* NOTE createCategoryTable() is implemented by PackageUserSchema for implementation sharing.
+ * I suppose you could argue since we're casting anyway that we could just make the required/shared
+ * methods public, but this works.
+ */
+ return packageUserSchema.createCategoryTable(this, categoryId, name);
+ }
+}
diff --git a/src/org/labkey/snd/PackageUserSchema.java b/src/org/labkey/snd/PackageUserSchema.java
new file mode 100644
index 0000000..acbe472
--- /dev/null
+++ b/src/org/labkey/snd/PackageUserSchema.java
@@ -0,0 +1,440 @@
+package org.labkey.snd;
+
+import org.apache.commons.collections4.MultiValuedMap;
+import org.apache.commons.collections4.multimap.AbstractMultiValuedMap;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.labkey.api.collections.CaseInsensitiveHashSet;
+import org.labkey.api.collections.CaseInsensitiveTreeSet;
+import org.labkey.api.data.AbstractTableInfo;
+import org.labkey.api.data.BaseColumnInfo;
+import org.labkey.api.data.ColumnInfo;
+import org.labkey.api.data.Container;
+import org.labkey.api.data.ContainerFilter;
+import org.labkey.api.data.JdbcType;
+import org.labkey.api.data.MutableColumnInfo;
+import org.labkey.api.data.SQLFragment;
+import org.labkey.api.data.SqlSelector;
+import org.labkey.api.data.TableInfo;
+import org.labkey.api.data.dialect.SqlDialect;
+import org.labkey.api.exp.PropertyColumn;
+import org.labkey.api.exp.property.Domain;
+import org.labkey.api.exp.property.DomainProperty;
+import org.labkey.api.exp.property.PropertyService;
+import org.labkey.api.query.AliasedColumn;
+import org.labkey.api.query.ExprColumn;
+import org.labkey.api.query.FieldKey;
+import org.labkey.api.query.QueryException;
+import org.labkey.api.query.QueryService;
+import org.labkey.api.query.SchemaKey;
+import org.labkey.api.query.UserSchema;
+import org.labkey.api.security.User;
+import org.labkey.api.snd.SNDDomainKind;
+import org.labkey.api.sql.LabKeySql;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+
+import static org.labkey.api.query.ExprColumn.STR_TABLE_ALIAS;
+
+public class PackageUserSchema extends UserSchema
+{
+ public static final String SCHEMA_NAME = "Packages";
+
+ final SNDUserSchema _snd;
+
+ public PackageUserSchema(SNDUserSchema parent)
+ {
+ super(new SchemaKey(parent.getSchemaPath(), SCHEMA_NAME), null, parent.getUser(), parent.getContainer(), parent.getDbSchema(), null);
+ _snd = parent;
+ }
+
+ @Override
+ public Set getSchemaNames()
+ {
+ return Set.of();
+ }
+
+ @Override
+ public Set getTableNames()
+ {
+ List list = new ArrayList<>();
+ visitAll(p -> {if (!p.superPkgIds.isEmpty()) list.add(p.description);});
+
+ // check for duplicates
+ Set duplicates = new CaseInsensitiveHashSet();
+ Set ret = new CaseInsensitiveTreeSet();
+ for (String s : list)
+ if (!ret.add(s))
+ duplicates.add(s);
+ ret.removeAll(duplicates);
+
+ return ret;
+ }
+
+
+ @Override
+ public @Nullable TableInfo createTable(String name, ContainerFilter cf)
+ {
+ return createPackageTable(name);
+ }
+
+
+ PackageTableInfo createPackageTable(String name)
+ {
+ // find domain for name
+ Map pkgs = new HashMap<>();
+ visitAll(p -> {
+ if (name.equalsIgnoreCase(p.description))
+ pkgs.put(p.packageId, p);
+ });
+ if (1 != pkgs.size())
+ return null;
+ var entry = pkgs.entrySet().iterator().next();
+ int packageId = entry.getKey();
+ String description = entry.getValue().description;
+ TableInfo eventData = _snd.getTable("EventData", null, true, true);
+ if (null == eventData)
+ return null;
+ return new PackageTableInfo(this, eventData, description, packageId);
+ }
+
+
+ PackageTableInfo createPackageTable(Package pkg)
+ {
+ int packageId = pkg.packageId;
+ String description = pkg.description;
+ TableInfo eventData = _snd.getTable("EventData", null, true, true);
+ if (null == eventData)
+ return null;
+ return new PackageTableInfo(this, eventData, description, packageId);
+ }
+
+
+ abstract static class _AbstractTableInfo extends AbstractTableInfo
+ {
+ final @NotNull UserSchema userSchema;
+
+ _AbstractTableInfo(@NotNull UserSchema schema, String name)
+ {
+ super(schema.getDbSchema(), name);
+ userSchema = schema;
+ }
+
+ @Override
+ public @NotNull UserSchema getUserSchema()
+ {
+ return userSchema;
+ }
+
+ public MutableColumnInfo addWrapColumn(TableInfo table, String name, ColumnInfo column)
+ {
+ assert column.getParentTable() == table : "Column is not from the same \"real\" table";
+ BaseColumnInfo ret = new AliasedColumn(this, name, column);
+ ret.setHidden(column.isHidden());
+ addColumn(ret);
+ return ret;
+ }
+
+ public void addWrapAllColumns(TableInfo table)
+ {
+ for (var col : table.getColumns())
+ {
+ addWrapColumn(table, col.getName(), col);
+ }
+ }
+ }
+
+
+ class PackageTableInfo extends _AbstractTableInfo
+ {
+ final int packageId;
+ final TableInfo eventData;
+
+ PackageTableInfo(PackageUserSchema schema, TableInfo eventData, String packageName, int packageId)
+ {
+ super(schema, packageName);
+ setName(packageName);
+ this.packageId = packageId;
+ this.eventData = eventData;
+ }
+
+
+ /* TODO: duplicate code StudyUtils is not public (add to Study class?) */
+ public static SQLFragment sequenceNumFromDateSQL(SqlDialect dialect, SQLFragment dateSql)
+ {
+ // SqlDialect.getDatePart() should not convert SQLFragment to String
+ if (!dateSql.getParams().isEmpty())
+ throw new IllegalStateException();
+ // Returns a SQL statement that produces a single number from a date, in the form of YYYYMMDD.
+ SQLFragment sql = new SQLFragment();
+ sql.append("CAST((10000 * ").append(dialect.getDatePart(Calendar.YEAR, dateSql)).append(") + ");
+ sql.append("(100 * ").append(dialect.getDatePart(Calendar.MONTH, dateSql)).append(") + ");
+ sql.append("(").append(dialect.getDatePart(Calendar.DAY_OF_MONTH, dateSql)).append(") AS NUMERIC(15,4))");
+ return sql;
+ }
+
+ @Override
+ protected void initializeColumns()
+ {
+ TableInfo events = getSchema().getTable("Events");
+
+ addColumn(new AliasedColumn(this, "SubjectId", events.getColumn("SubjectId")));
+ addColumn(new AliasedColumn(this, "Date", events.getColumn("Date")));
+ addColumn(new AliasedColumn(this, "QcState", events.getColumn("QcState")));
+ var date = new SQLFragment(events.getColumn("Date").getValueSql(STR_TABLE_ALIAS));
+ var seqnumSQL = sequenceNumFromDateSQL(getDbSchema().getSqlDialect(), date);
+ var seqnumCol = new ExprColumn(this, "SequenceNum", seqnumSQL, JdbcType.DECIMAL);
+ seqnumCol.setHidden(true);
+ addColumn(seqnumCol);
+
+ addWrapAllColumns(eventData);
+
+ Package p = getPackage(packageId);
+
+ Container c = getUserSchema().getContainer();
+ User user = getUserSchema().getUser();
+ ColumnInfo object = Objects.requireNonNull(getColumn("ObjectURI", false));
+ if (p.domain != null)
+ {
+ for (DomainProperty dp : p.domain.getProperties())
+ {
+ if (null == getColumn(dp.getName(), false))
+ {
+ PropertyColumn column = new PropertyColumn(dp.getPropertyDescriptor(), object, c, user, false);
+ addColumn(column);
+ }
+ }
+ }
+ }
+
+ static Set eventDataCols = CaseInsensitiveHashSet.of("subjectid", "date", "qcstate", "sequencenum");
+
+ @Override
+ protected SQLFragment getFromSQLExpanded(String alias, Set cols)
+ {
+ boolean hasEventDataColumn = cols.stream().anyMatch(fk -> eventDataCols.contains(fk.getParts().get(0)));
+ return getFromSQLJoin(alias, hasEventDataColumn);
+ }
+
+ @Override
+ public @NotNull SQLFragment getFromSQL(String alias)
+ {
+ return getFromSQLJoin(alias, true);
+ }
+
+ @Override
+ public @NotNull SQLFragment getFromSQL()
+ {
+ throw new IllegalStateException();
+ }
+
+ public @NotNull SQLFragment getFromSQLJoin(String alias, boolean withEventJoin)
+ {
+ SqlDialect dialect = getDbSchema().getSqlDialect();
+ var me = getPackage(packageId);
+ TableInfo events = getSchema().getTable("Events");
+ var fromSql = new SQLFragment("(SELECT ");
+ if (withEventJoin)
+ fromSql.append("events.SubjectId, events.Date, events.QcState, ");
+ fromSql.append("eventdata.*\n");
+ fromSql.append("FROM ").append(eventData.getFromSQL("eventdata"));
+ if (withEventJoin)
+ fromSql.append(" INNER JOIN ").append(events.getFromSQL("events")).append( " ON eventdata.eventid = events.eventid\n");
+ fromSql.append("WHERE eventdata.Container=").appendValue(getContainer());
+ if (null == me || me.superPkgIds.isEmpty())
+ fromSql.append(" AND (0=1)");
+ else
+ {
+ fromSql.append(" AND SuperPkgId ");
+ dialect.appendInClauseSql(fromSql, me.superPkgIds);
+ }
+ return fromSql.append(") ").append(alias).append("\n");
+ }
+ }
+
+
+ /*
+ * Package helpers
+ * CONSIDER: move to a cache in SNDManager
+ */
+
+ public record SuperPkg(int superPkgId, Integer parentSuperPkgId, int pkgId, String description) {}
+ public static class Package
+ {
+ Package(int packageId, String description, Domain d)
+ {
+ this.packageId = packageId;
+ this.description = description;
+ this.domain = d;
+ }
+ final int packageId;
+ final String description;
+ final Domain domain;
+ List superPkgIds = new ArrayList<>();
+ }
+ Map packagesMap;
+
+ void initPackages()
+ {
+ if (null == packagesMap)
+ {
+ List supers = new SqlSelector(getDbSchema(), new SQLFragment(
+ new SQLFragment("SELECT SuperPkgId, ParentSuperPkgId, Pkgs.PkgId, Description FROM snd.Pkgs INNER JOIN snd.SuperPkgs ON Pkgs.PkgId = SuperPkgs.PkgId WHERE Pkgs.Container = ").appendValue(getContainer())
+ )).getArrayList(SuperPkg.class);
+ Map map = new HashMap<>();
+ supers.forEach(superPkg -> {
+ var package_ = map.computeIfAbsent(superPkg.pkgId, id ->
+ {
+ String uri = SNDDomainKind.formatSndDomainURI(getContainer().getRowId(), superPkg.pkgId);
+ var domain = PropertyService.get().getDomain(getContainer(), uri);
+ return new Package(superPkg.pkgId, superPkg.description, domain);
+ });
+ package_.superPkgIds.add(superPkg.superPkgId);
+ });
+ packagesMap = map;
+ }
+ }
+
+ Package getPackage(int id)
+ {
+ initPackages();
+ return packagesMap.get(id);
+ }
+
+ void visitAll(Consumer fn)
+ {
+ initPackages();
+ packagesMap.values().forEach(fn);
+ }
+
+
+ TableInfo createCategoryTable(UserSchema categoriesSchema, int categoryId, String name)
+ {
+ // use getTableNames() as it does duplicate description detection
+ Set tableNames = new HashSet<>(getTableNames());
+ // get a tableInfo for each packages in category
+ List pkgs = new SqlSelector(getDbSchema(), new SQLFragment("SELECT DISTINCT PkgId FROM snd.PkgCategoryJunction WHERE CategoryId=").appendValue(categoryId))
+ .stream(Integer.class)
+ .map(I -> getPackage(I.intValue()))
+ .filter(pkg -> null != pkg && tableNames.contains(pkg.description))
+ .map(this::createPackageTable)
+ .toList();
+ if (pkgs.isEmpty())
+ return null;
+
+ // This is _almost_ ArrayListValuedHashMap, but I want to track the keyset insertion order.
+ MultiValuedMap map = new AbstractMultiValuedMap<>(new LinkedHashMap<>())
+ {
+ @Override
+ protected Collection createCollection()
+ {
+ return new ArrayList<>();
+ }
+ };
+ for (TableInfo t : pkgs)
+ {
+ for (ColumnInfo col : t.getColumns())
+ {
+ // CONSIDER: consider optimize the snd.evens join by hoisting it out of the UNION
+ // e.g. if (PackageTableInfo.eventDataCols.contains(col.getName())) continue;
+ map.put(col.getFieldKey(), col);
+ }
+ }
+ var keys = new HashSet<>(map.keySet());
+ keys.forEach(key -> {
+ var coll = map.get(key);
+ if (coll.size() != pkgs.size())
+ {
+ map.remove(key);
+ return;
+ }
+ var opt = coll.stream().findFirst();
+ final var jdbcType = opt.isPresent() ? opt.get().getJdbcType() : JdbcType.OTHER;
+ var allMatch = coll.stream().allMatch(c -> jdbcType == c.getJdbcType());
+ if (!allMatch)
+ map.remove(key);
+ });
+ if (map.isEmpty())
+ return null;
+
+ return new CategoriesTable(categoriesSchema, categoryId, name, pkgs, map);
+ }
+
+ public class CategoriesTable extends _AbstractTableInfo
+ {
+ final int categoryId;
+ final List pkgTables;
+ final MultiValuedMap columnMap;
+ TableInfo queryTableInfo = null;
+
+ CategoriesTable(UserSchema categoriesSchema, int categoryId, String name, List pkgs, MultiValuedMap columnMap)
+ {
+ super(categoriesSchema, name);
+ this.categoryId = categoryId;
+ this.pkgTables = pkgs;
+ this.columnMap = columnMap;
+ }
+
+ @Override
+ protected void initializeColumns()
+ {
+ var hidden = CaseInsensitiveHashSet.of("Container", "LSID", "ObjectId", "ObjectURI", "SequenceNum");
+
+ // It's unfortunate that SND doesn't share property descriptors, we have to UNION each "package" table to get the "category" table.
+ // this is LabKey SQL (not using SQLFragment for this)
+ StringBuilder lkUnionSQL = new StringBuilder();
+ var unionAll = "";
+ var colNames = columnMap.keySet().toArray(new FieldKey[0]);
+ for (PackageTableInfo pkgTable : pkgTables)
+ {
+ lkUnionSQL.append(unionAll);
+ unionAll = "\nUNION ALL\n";
+ var comma = "";
+ lkUnionSQL.append("SELECT ");
+ for (FieldKey colName : colNames)
+ {
+ lkUnionSQL.append(comma);
+ comma = ", ";
+ lkUnionSQL.append(LabKeySql.quoteIdentifier(colName.getName()));
+ // LabKey SQL unhides columns by default
+ if (hidden.contains(colName.getName()))
+ lkUnionSQL.append(" @hidden");
+ }
+ lkUnionSQL.append("\nFROM ").append("Packages.").append(LabKeySql.quoteIdentifier(pkgTable.getName()));
+ }
+
+ var qdef = QueryService.get().createQueryDef(getUser(), getContainer(), _snd, "_union_");
+ qdef.setSql(lkUnionSQL.toString());
+ List errors = new ArrayList<>();
+ queryTableInfo = qdef.getTable(_snd, errors, false);
+ if (!errors.isEmpty())
+ throw errors.get(0);
+ if (null == queryTableInfo)
+ throw new QueryException("Unexpected error compiling query");
+
+ addWrapAllColumns(queryTableInfo);
+ }
+
+ @Override
+ public @NotNull SQLFragment getFromSQL(String alias)
+ {
+ return queryTableInfo.getFromSQL(alias);
+ }
+
+ @Override
+ protected SQLFragment getFromSQL()
+ {
+ throw new IllegalStateException();
+ }
+ }
+}
diff --git a/src/org/labkey/snd/SNDManager.java b/src/org/labkey/snd/SNDManager.java
index a88df60..bdd55f6 100644
--- a/src/org/labkey/snd/SNDManager.java
+++ b/src/org/labkey/snd/SNDManager.java
@@ -68,6 +68,7 @@
import org.labkey.api.security.User;
import org.labkey.api.security.roles.FolderAdminRole;
import org.labkey.api.security.roles.RoleManager;
+import org.labkey.api.settings.AppProps;
import org.labkey.api.settings.LookAndFeelProperties;
import org.labkey.api.snd.AttributeData;
import org.labkey.api.snd.Category;
diff --git a/src/org/labkey/snd/SNDModule.java b/src/org/labkey/snd/SNDModule.java
index 37a5813..7c1f285 100644
--- a/src/org/labkey/snd/SNDModule.java
+++ b/src/org/labkey/snd/SNDModule.java
@@ -67,7 +67,7 @@ public String getName()
@Override
public @Nullable Double getSchemaVersion()
{
- return 24.000;
+ return 25.000;
}
@Override
diff --git a/src/org/labkey/snd/SNDUserSchema.java b/src/org/labkey/snd/SNDUserSchema.java
index b4c9e45..98e211b 100644
--- a/src/org/labkey/snd/SNDUserSchema.java
+++ b/src/org/labkey/snd/SNDUserSchema.java
@@ -26,10 +26,12 @@
import org.labkey.api.data.TableInfo;
import org.labkey.api.data.TableSelector;
import org.labkey.api.query.FieldKey;
+import org.labkey.api.query.QuerySchema;
import org.labkey.api.query.SimpleUserSchema;
import org.labkey.api.query.UserSchema;
import org.labkey.api.security.User;
import org.labkey.api.security.roles.Role;
+import org.labkey.snd.query.AbstractSNDTableInfo;
import org.labkey.snd.query.AttributeDataTable;
import org.labkey.snd.query.CategoriesTable;
import org.labkey.snd.query.EventDataTable;
@@ -66,6 +68,26 @@ public SNDUserSchema(String name, @Nullable String description, User user, Conta
_contextualRole = contextualRole;
}
+ @Override
+ public Set getSchemaNames()
+ {
+ if (_restricted)
+ return Set.of();
+ return Set.of(PackageUserSchema.SCHEMA_NAME, CategoryUserSchema.SCHEMA_NAME);
+ }
+
+ @Override
+ public QuerySchema getSchema(String name)
+ {
+ if (_restricted)
+ return null;
+ if (PackageUserSchema.SCHEMA_NAME.equalsIgnoreCase(name))
+ return new PackageUserSchema(this);
+ if (CategoryUserSchema.SCHEMA_NAME.equalsIgnoreCase(name))
+ return new CategoryUserSchema(this);
+ return super.getSchema(name);
+ }
+
@Override
public @NotNull Set getContextualRoles()
{
@@ -79,7 +101,7 @@ public enum TableType
@Override
public TableInfo createTable(SNDUserSchema schema, ContainerFilter cf)
{
- return new SuperPackagesTable(schema, SNDSchema.getInstance().getTableInfoSuperPkgs(), cf).init();
+ return new SuperPackagesTable(schema, SNDSchema.getInstance().getTableInfoSuperPkgs()).init();
}
},
Pkgs
@@ -87,7 +109,7 @@ public TableInfo createTable(SNDUserSchema schema, ContainerFilter cf)
@Override
public TableInfo createTable(SNDUserSchema schema, ContainerFilter cf)
{
- return new PackagesTable(schema, SNDSchema.getInstance().getTableInfoPkgs(), cf).init();
+ return new PackagesTable(schema, SNDSchema.getInstance().getTableInfoPkgs()).init();
}
},
PkgCategories
@@ -104,10 +126,8 @@ public TableInfo createTable(SNDUserSchema schema, ContainerFilter cf)
@Override
public TableInfo createTable(SNDUserSchema schema, ContainerFilter cf)
{
- SimpleUserSchema.SimpleTable table =
- new SimpleUserSchema.SimpleTable<>(
- schema, SNDSchema.getInstance().getTableInfoPkgCategoryJunction(), cf).init();
-
+ var table = new AbstractSNDTableInfo(schema, SNDSchema.getInstance().getTableInfoPkgCategoryJunction()) {};
+ table.init();
return table;
}
},
@@ -116,10 +136,8 @@ public TableInfo createTable(SNDUserSchema schema, ContainerFilter cf)
@Override
public TableInfo createTable(SNDUserSchema schema, ContainerFilter cf)
{
- SimpleUserSchema.SimpleTable table =
- new SimpleUserSchema.SimpleTable<>(
- schema, SNDSchema.getInstance().getTableInfoProjectItems(), cf).init();
-
+ var table = new AbstractSNDTableInfo(schema, SNDSchema.getInstance().getTableInfoProjectItems()) {};
+ table.init();
return table;
}
},
@@ -138,7 +156,7 @@ public TableInfo createTable(SNDUserSchema schema, ContainerFilter cf)
{
if (schema.getContainer().hasPermission(schema.getUser(), SNDViewerPermission.class, schema.getContextualRoles()))
{
- return new EventsTable(schema, SNDSchema.getInstance().getTableInfoEvents(), cf).init();
+ return new EventsTable(schema, SNDSchema.getInstance().getTableInfoEvents()).init();
}
return null;
}
@@ -150,7 +168,7 @@ public TableInfo createTable(SNDUserSchema schema, ContainerFilter cf)
{
if (schema.getContainer().hasPermission(schema.getUser(), SNDViewerPermission.class, schema.getContextualRoles()))
{
- return new EventNotesTable(schema, SNDSchema.getInstance().getTableInfoEventNotes(), cf).init();
+ return new EventNotesTable(schema, SNDSchema.getInstance().getTableInfoEventNotes()).init();
}
return null;
@@ -163,7 +181,7 @@ public TableInfo createTable(SNDUserSchema schema, ContainerFilter cf)
{
if (schema.getContainer().hasPermission(schema.getUser(), SNDViewerPermission.class, schema.getContextualRoles()))
{
- return new EventDataTable(schema, SNDSchema.getInstance().getTableInfoEventData(), cf).init();
+ return new EventDataTable(schema, SNDSchema.getInstance().getTableInfoEventData()).init();
}
return null;
@@ -218,7 +236,7 @@ public TableInfo createTable(SNDUserSchema schema, ContainerFilter cf)
{
if (schema.getContainer().hasPermission(schema.getUser(), SNDViewerPermission.class, schema.getContextualRoles()))
{
- return new EventsCacheTable(schema, SNDSchema.getInstance().getTableInfoEventsCache(), cf).init();
+ return new EventsCacheTable(schema, SNDSchema.getInstance().getTableInfoEventsCache()).init();
}
return null;
diff --git a/src/org/labkey/snd/query/AbstractSNDTableInfo.java b/src/org/labkey/snd/query/AbstractSNDTableInfo.java
new file mode 100644
index 0000000..707efc9
--- /dev/null
+++ b/src/org/labkey/snd/query/AbstractSNDTableInfo.java
@@ -0,0 +1,26 @@
+package org.labkey.snd.query;
+
+import org.labkey.api.data.TableInfo;
+import org.labkey.api.query.SimpleUserSchema;
+import org.labkey.api.util.ContainerContext;
+import org.labkey.snd.SNDUserSchema;
+
+abstract public class AbstractSNDTableInfo extends SimpleUserSchema.SimpleTable
+{
+ protected AbstractSNDTableInfo(SNDUserSchema schema, TableInfo dbtable)
+ {
+ super(schema, dbtable, null);
+ }
+
+ @Override
+ public boolean supportsContainerFilter()
+ {
+ return false;
+ }
+
+ @Override
+ public ContainerContext getContainerContext()
+ {
+ return getUserSchema().getContainer();
+ }
+}
diff --git a/src/org/labkey/snd/query/AttributeDataTable.java b/src/org/labkey/snd/query/AttributeDataTable.java
index 4bb2d03..82aa483 100644
--- a/src/org/labkey/snd/query/AttributeDataTable.java
+++ b/src/org/labkey/snd/query/AttributeDataTable.java
@@ -46,6 +46,7 @@
import org.labkey.api.security.UserPrincipal;
import org.labkey.api.security.permissions.Permission;
import org.labkey.api.settings.AppProps;
+import org.labkey.api.snd.SNDDomainKind;
import org.labkey.api.snd.SNDService;
import org.labkey.api.util.UnexpectedException;
import org.labkey.snd.SNDManager;
@@ -122,16 +123,13 @@ public SQLFragment getFromSQL(String alias)
sql.append(" INNER JOIN ");
sql.append(OntologyManager.getTinfoPropertyDescriptor(), "pd");
// Filter to include only properties associated with packages
- sql.append(" ON x.PropertyId = pd.PropertyId AND pd.PropertyURI LIKE ? INNER JOIN ");
+ sql.append(" ON x.PropertyId = pd.PropertyId AND pd.PropertyURI ").append(SNDDomainKind.likeSndDomainURI(null,null)).append(" INNER JOIN ");
// Filter to include only values associated with EventDatas
sql.append(SNDSchema.getInstance().getTableInfoEventData(), "ed");
sql.append(" ON ed.ObjectURI = o.ObjectURI ");
sql.append(") ");
sql.append(alias);
- // Note - this must be kept in sync with the PropertyURIs generated for the packages
- sql.add("urn:lsid:" + AppProps.getInstance().getDefaultLsidAuthority() + ":package-snd.Folder-%");
-
return sql;
}
@@ -399,7 +397,7 @@ public int truncateRows(User user, Container container, @Nullable Map
+public class EventDataTable extends AbstractSNDTableInfo
{
/**
* Create the simple table.
@@ -60,9 +62,36 @@ public class EventDataTable extends SimpleUserSchema.SimpleTable
* @param schema
* @param table
*/
- public EventDataTable(SNDUserSchema schema, TableInfo table, ContainerFilter cf)
+ public EventDataTable(SNDUserSchema schema, TableInfo table)
{
- super(schema, table, cf);
+ super(schema, table);
+ }
+
+ public void addColumns()
+ {
+ BaseColumnInfo objectid = new BaseColumnInfo("ObjectId", this, JdbcType.INTEGER)
+ {
+ @Override
+ public SQLFragment getValueSql(String tableAliasName)
+ {
+ return new SQLFragment(tableAliasName).append(".").append("ObjectId");
+ }
+ };
+ objectid.setHidden(true);
+ objectid.setFk(new BaseColumnInfo.SchemaForeignKey(objectid, "exp", "Object", "ObjectId", false));
+ // SimpleTableSchema.SimpleTable.wrapColumn() is weird. It calls addColumn() which is not the usual pattern.
+ fixupWrappedColumn(objectid, objectid);
+ addColumn(objectid);
+ super.addColumns();
+ }
+
+ @Override
+ public @NotNull SQLFragment getFromSQL(String alias)
+ {
+ SQLFragment table = super.getFromSQL("_evnt_data_");
+ SQLFragment join = new SQLFragment("(SELECT _evnt_data_.*, ObjectId FROM ")
+ .append(table).append(" INNER JOIN exp.Object ON _evnt_data_.ObjectURI = Object.ObjectURI").append(") ").append(alias);
+ return join;
}
@Override
@@ -257,9 +286,9 @@ private int deleteFromExpObjectProperty(Logger log)
SqlExecutor executor = new SqlExecutor(_expSchema);
SQLFragment truncObjProp = new SQLFragment("delete from " + _expSchema.getName() + ".ObjectProperty\n");
truncObjProp.append("where objectId in\n");
- truncObjProp.append("(select objectId from exp.object where objectURI like '%urn:lsid:"+ defaultLsidAuthority +":SND.EventData.Folder%')\n");
+ truncObjProp.append("(select objectId from exp.object where objectURI like ").appendValue("%urn:lsid:"+ defaultLsidAuthority +":SND.EventData.Folder%").append("\n");
truncObjProp.append("and propertyId in\n");
- truncObjProp.append("(select propertyId from exp.propertyDescriptor where PropertyURI like '%urn:lsid:"+ defaultLsidAuthority +":package-snd.Folder%')");
+ truncObjProp.append("(select propertyId from exp.propertyDescriptor where PropertyURI ").append(SNDDomainKind.likeSndDomainURI(null,null));
numDeletedRows = executor.execute(truncObjProp);
tx.commit();
}
diff --git a/src/org/labkey/snd/query/EventNotesTable.java b/src/org/labkey/snd/query/EventNotesTable.java
index a0bd74d..2995536 100644
--- a/src/org/labkey/snd/query/EventNotesTable.java
+++ b/src/org/labkey/snd/query/EventNotesTable.java
@@ -40,7 +40,7 @@
import java.util.List;
import java.util.Map;
-public class EventNotesTable extends SimpleUserSchema.SimpleTable
+public class EventNotesTable extends AbstractSNDTableInfo
{
/**
* Create the simple table.
@@ -49,9 +49,9 @@ public class EventNotesTable extends SimpleUserSchema.SimpleTable
* @param schema
* @param table
*/
- public EventNotesTable(SNDUserSchema schema, TableInfo table, ContainerFilter cf)
+ public EventNotesTable(SNDUserSchema schema, TableInfo table)
{
- super(schema, table, cf);
+ super(schema, table);
}
@Override
diff --git a/src/org/labkey/snd/query/EventsCacheTable.java b/src/org/labkey/snd/query/EventsCacheTable.java
index c0ec01e..2be1d3c 100644
--- a/src/org/labkey/snd/query/EventsCacheTable.java
+++ b/src/org/labkey/snd/query/EventsCacheTable.java
@@ -30,7 +30,7 @@
import java.util.ArrayList;
import java.util.List;
-public class EventsCacheTable extends SimpleUserSchema.SimpleTable
+public class EventsCacheTable extends AbstractSNDTableInfo
{
/**
* Create the simple table.
@@ -39,9 +39,9 @@ public class EventsCacheTable extends SimpleUserSchema.SimpleTable defaultVisibleColumns = new ArrayList<>();
diff --git a/src/org/labkey/snd/query/EventsTable.java b/src/org/labkey/snd/query/EventsTable.java
index 5b511a6..259da80 100644
--- a/src/org/labkey/snd/query/EventsTable.java
+++ b/src/org/labkey/snd/query/EventsTable.java
@@ -53,7 +53,7 @@
import java.util.Map;
import java.util.Set;
-public class EventsTable extends SimpleTable
+public class EventsTable extends AbstractSNDTableInfo
{
/**
* Create the simple table.
@@ -64,9 +64,15 @@ public class EventsTable extends SimpleTable
*/
private final SNDManager _sndManager = SNDManager.get();
- public EventsTable(SNDUserSchema schema, TableInfo table, ContainerFilter cf)
+ public EventsTable(SNDUserSchema schema, TableInfo table)
{
- super(schema, table, cf);
+ super(schema, table);
+ }
+
+ @Override
+ public boolean supportsContainerFilter()
+ {
+ return super.supportsContainerFilter();
}
@Override
diff --git a/src/org/labkey/snd/query/LookupsTable.java b/src/org/labkey/snd/query/LookupsTable.java
index 0537a74..e29ed49 100644
--- a/src/org/labkey/snd/query/LookupsTable.java
+++ b/src/org/labkey/snd/query/LookupsTable.java
@@ -20,29 +20,24 @@
import org.labkey.api.data.ContainerFilter;
import org.labkey.api.data.JdbcType;
import org.labkey.api.data.SQLFragment;
-import org.labkey.api.data.SimpleFilter;
import org.labkey.api.data.TableInfo;
-import org.labkey.api.data.TableSelector;
import org.labkey.api.dataiterator.DataIteratorBuilder;
import org.labkey.api.exp.OntologyManager;
import org.labkey.api.query.BatchValidationException;
import org.labkey.api.query.ExprColumn;
-import org.labkey.api.query.FieldKey;
import org.labkey.api.query.InvalidKeyException;
import org.labkey.api.query.QueryUpdateService;
import org.labkey.api.query.QueryUpdateServiceException;
import org.labkey.api.query.SimpleUserSchema.SimpleTable;
import org.labkey.api.security.User;
-import org.labkey.api.settings.AppProps;
+import org.labkey.api.snd.SNDDomainKind;
import org.labkey.snd.SNDManager;
import org.labkey.snd.SNDSchema;
import org.labkey.snd.SNDUserSchema;
import java.sql.SQLException;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.Set;
public class LookupsTable extends SimpleTable
{
@@ -71,13 +66,12 @@ public LookupsTable init() {
isInUseQuery.append(" INNER JOIN ");
isInUseQuery.append(OntologyManager.getTinfoPropertyDescriptor(), "pd");
isInUseQuery.append(" ON ls.SetName = pd.LookupQuery ");
- isInUseQuery.append(" AND pd.PropertyURI LIKE ? ");
+ isInUseQuery.append(" AND pd.PropertyURI ").append(SNDDomainKind.likeSndDomainURI(null,null));
isInUseQuery.append(" INNER JOIN ");
isInUseQuery.append(OntologyManager.getTinfoObjectProperty(), "op");
isInUseQuery.append(" ON op.PropertyId = pd.PropertyId ");
isInUseQuery.append(" WHERE CAST(" + ExprColumn.STR_TABLE_ALIAS + ".LookupId AS FLOAT) = op.FloatValue) ");
isInUseQuery.append(" THEN 'true' else 'false' END)");
- isInUseQuery.add("urn:lsid:" + AppProps.getInstance().getDefaultLsidAuthority() + ":package-snd.Folder-%");
ExprColumn isInUseColumn = new ExprColumn(this, "IsInUse", isInUseQuery, JdbcType.BOOLEAN);
addColumn(isInUseColumn);
diff --git a/src/org/labkey/snd/query/PackagesTable.java b/src/org/labkey/snd/query/PackagesTable.java
index ae8bf93..fc9686b 100644
--- a/src/org/labkey/snd/query/PackagesTable.java
+++ b/src/org/labkey/snd/query/PackagesTable.java
@@ -63,7 +63,7 @@
/**
* Created by marty on 8/23/2017.
*/
-public class PackagesTable extends SimpleTable
+public class PackagesTable extends AbstractSNDTableInfo
{
/**
@@ -73,9 +73,9 @@ public class PackagesTable extends SimpleTable
* @param schema
* @param table
*/
- public PackagesTable(SNDUserSchema schema, TableInfo table, ContainerFilter cf)
+ public PackagesTable(SNDUserSchema schema, TableInfo table)
{
- super(schema, table, cf);
+ super(schema, table);
}
@Override
diff --git a/src/org/labkey/snd/query/SuperPackagesTable.java b/src/org/labkey/snd/query/SuperPackagesTable.java
index ad1ca12..be4fdeb 100644
--- a/src/org/labkey/snd/query/SuperPackagesTable.java
+++ b/src/org/labkey/snd/query/SuperPackagesTable.java
@@ -50,7 +50,7 @@
/**
* Created by marty on 8/23/2017.
*/
-public class SuperPackagesTable extends SimpleTable
+public class SuperPackagesTable extends AbstractSNDTableInfo
{
/**
@@ -60,9 +60,9 @@ public class SuperPackagesTable extends SimpleTable
* @param schema
* @param table
*/
- public SuperPackagesTable(SNDUserSchema schema, TableInfo table, ContainerFilter cf)
+ public SuperPackagesTable(SNDUserSchema schema, TableInfo table)
{
- super(schema, table, cf);
+ super(schema, table);
}
@Override