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