Author: pcl
Date: Tue Jan 2 18:44:57 2007
New Revision: 492024
URL: http://svn.apache.org/viewvc?view=rev&rev=492024
Log:
Added new SchemaTool action: deleteTableContents, and added support for
comma-separated lists of schema actions, both via the MappingTool -schemaAction
option and via direct SchemaTool usage. This implementation always processes
all of the known types in the system; I have not tested running MappingTool
against just one or two classes to see what 'all of the known types in the
system' really means.
Modified:
incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java
incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingTool.java
incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/SchemaTool.java
incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java
incubator/openjpa/trunk/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/schema/localizer.properties
incubator/openjpa/trunk/openjpa-project/src/doc/manual/ref_guide_dbsetup.xml
incubator/openjpa/trunk/openjpa-project/src/doc/manual/ref_guide_mapping.xml
Modified:
incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java
URL:
http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java?view=diff&rev=492024&r1=492023&r2=492024
==============================================================================
---
incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java
(original)
+++
incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java
Tue Jan 2 18:44:57 2007
@@ -468,7 +468,7 @@
// this case, we need to perform the query in-memory and
// manually delete the instances
if (updates == null)
- sql[i] = dict.toDelete(mappings[i], sel, _store, params);
+ sql[i] = dict.toDelete(mappings[i], sel, params);
else
sql[i] = dict.toUpdate(mappings[i], sel, _store, params,
updates);
Modified:
incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingTool.java
URL:
http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingTool.java?view=diff&rev=492024&r1=492023&r2=492024
==============================================================================
---
incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingTool.java
(original)
+++
incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingTool.java
Tue Jan 2 18:44:57 2007
@@ -102,7 +102,7 @@
private MappingRepository _repos = null;
private SchemaGroup _schema = null;
private SchemaTool _schemaTool = null;
- private String _schemaAction = SchemaTool.ACTION_ADD;
+ private String _schemaActions = SchemaTool.ACTION_ADD;
private boolean _readSchema = false;
private boolean _pks = false;
private boolean _fks = false;
@@ -159,20 +159,20 @@
/**
* The schema modification policy, or <code>none</code>. See the
- * ACTION constants in [EMAIL PROTECTED] SchemaTool}. Defaults to
- * [EMAIL PROTECTED] SchemaTool#ACTION_ADD}.
+ * ACTION constants in [EMAIL PROTECTED] SchemaTool}. May be a
comma-separated
+ * list of values. Defaults to [EMAIL PROTECTED] SchemaTool#ACTION_ADD}.
*/
public String getSchemaAction() {
- return _schemaAction;
+ return _schemaActions;
}
/**
* The schema modification policy, or <code>none</code>. See the
- * ACTION constants in [EMAIL PROTECTED] SchemaTool}. Defaults to
- * [EMAIL PROTECTED] SchemaTool#ACTION_ADD}.
+ * ACTION constants in [EMAIL PROTECTED] SchemaTool}. May be a
comma-separated
+ * list of values. Defaults to [EMAIL PROTECTED] SchemaTool#ACTION_ADD}.
*/
public void setSchemaAction(String schemaAction) {
- _schemaAction = schemaAction;
+ _schemaActions = schemaAction;
}
/**
@@ -294,15 +294,6 @@
/**
* Return the schema tool to use for schema modification.
*/
- public SchemaTool getSchemaTool() {
- if (_schemaTool == null)
- _schemaTool = newSchemaTool(_schemaAction);
- return _schemaTool;
- }
-
- /**
- * Return the schema tool to use for schema modification.
- */
private SchemaTool newSchemaTool(String action) {
if (SCHEMA_ACTION_NONE.equals(action))
action = null;
@@ -394,14 +385,14 @@
*/
public SchemaGroup getSchemaGroup() {
if (_schema == null) {
- if (ACTION_BUILD_SCHEMA.equals(_action)) {
+ if (_action.contains(ACTION_BUILD_SCHEMA)) {
DynamicSchemaFactory factory = new DynamicSchemaFactory();
factory.setConfiguration(_conf);
_schema = factory;
- } else if (_readSchema
- || SchemaTool.ACTION_RETAIN.equals(_schemaAction)
- || SchemaTool.ACTION_REFRESH.equals(_schemaAction)) {
- _schema = (SchemaGroup) getSchemaTool().getDBSchemaGroup().
+ } else if (_readSchema
+ || _schemaActions.contains(SchemaTool.ACTION_RETAIN)
+ || _schemaActions.contains(SchemaTool.ACTION_REFRESH)) {
+ _schema = (SchemaGroup) newSchemaTool(null).getDBSchemaGroup().
clone();
} else {
// with this we'll just read tables as different mappings
@@ -449,6 +440,10 @@
* involves clearing the internal mapping repository.
*/
public void record() {
+ record(null);
+ }
+
+ private void record(MappingTool.Flags flags) {
MappingRepository repos = getRepository();
MetaDataFactory io = repos.getMetaDataFactory();
ClassMapping[] mappings;
@@ -477,13 +472,25 @@
// now run the schematool as long as we're doing some schema
// action and the user doesn't just want an xml output
- if (!SCHEMA_ACTION_NONE.equals(_schemaAction)
- && (_schemaWriter == null || (_schemaTool != null
- && _schemaTool.getWriter() != null))) {
- SchemaTool tool = getSchemaTool();
- tool.setSchemaGroup(getSchemaGroup());
- tool.run();
- tool.record();
+ String[] schemaActions = _schemaActions.split(",");
+ for (int i = 0; i < schemaActions.length; i++) {
+ if (!SCHEMA_ACTION_NONE.equals(schemaActions[i])
+ && (_schemaWriter == null || (_schemaTool != null
+ && _schemaTool.getWriter() != null))) {
+ SchemaTool tool = newSchemaTool(schemaActions[i]);
+
+ // configure the tool with additional settings
+ if (flags != null) {
+ tool.setDropTables(flags.dropTables);
+ tool.setDropSequences(flags.dropSequences);
+ tool.setWriter(flags.sqlWriter);
+ tool.setOpenJPATables(flags.openjpaTables);
+ }
+
+ tool.setSchemaGroup(getSchemaGroup());
+ tool.run();
+ tool.record();
+ }
}
// xml output of schema?
@@ -716,8 +723,8 @@
MappingRepository repos = getRepository();
repos.setStrategyInstaller(new RuntimeStrategyInstaller(repos));
if (getMapping(repos, cls, true) != null)
- _flushSchema = !SCHEMA_ACTION_NONE.equals(_schemaAction)
- && !SchemaTool.ACTION_ADD.equals(_schemaAction);
+ _flushSchema = !_schemaActions.contains(SCHEMA_ACTION_NONE)
+ && !_schemaActions.contains(SchemaTool.ACTION_ADD);
}
/**
@@ -761,7 +768,7 @@
if (_dropCls == null)
_dropCls = new HashSet();
_dropCls.add(cls);
- if (!SchemaTool.ACTION_DROP.equals(_schemaAction))
+ if (!_schemaActions.contains(SchemaTool.ACTION_DROP))
return;
MappingRepository repos = getRepository();
@@ -1044,13 +1051,6 @@
tool.setIndexes(flags.indexes);
tool.setSequences(flags.sequences || flags.dropSequences);
- // make sure to do this after other settings so that other settings
- // are passed on to schema tool
- tool.getSchemaTool().setDropTables(flags.dropTables);
- tool.getSchemaTool().setDropSequences(flags.dropSequences);
- tool.getSchemaTool().setWriter(flags.sqlWriter);
- tool.getSchemaTool().setOpenJPATables(flags.openjpaTables);
-
// and run the action
for (int i = 0; i < act.length; i++) {
log.info(_loc.get("tool-running", act[i], flags.action));
@@ -1059,7 +1059,7 @@
tool.run(act[i]);
}
log.info(_loc.get("tool-record"));
- tool.record();
+ tool.record(flags);
return true;
}
Modified:
incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/SchemaTool.java
URL:
http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/SchemaTool.java?view=diff&rev=492024&r1=492023&r2=492024
==============================================================================
---
incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/SchemaTool.java
(original)
+++
incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/schema/SchemaTool.java
Tue Jan 2 18:44:57 2007
@@ -27,6 +27,7 @@
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Set;
import javax.sql.DataSource;
@@ -65,6 +66,8 @@
public static final String ACTION_DROPDB = "dropDB";
public static final String ACTION_IMPORT = "import";
public static final String ACTION_EXPORT = "export";
+ public static final String ACTION_DELETE_TABLE_CONTENTS =
+ "deleteTableContents";
public static final String[] ACTIONS = new String[]{
ACTION_ADD,
@@ -77,6 +80,7 @@
ACTION_DROPDB,
ACTION_IMPORT,
ACTION_EXPORT,
+ ACTION_DELETE_TABLE_CONTENTS,
};
private static final Localizer _loc = Localizer.forPackage
@@ -324,6 +328,8 @@
createDB();
else if (ACTION_DROPDB.equals(_action))
dropDB();
+ else if (ACTION_DELETE_TABLE_CONTENTS.equals(_action))
+ deleteTableContents();
}
/**
@@ -400,6 +406,26 @@
}
/**
+ * Issue DELETE statement against all known tables.
+ */
+ private void deleteTableContents()
+ throws SQLException {
+ SchemaGroup group = getSchemaGroup();
+ Schema[] schemas = group.getSchemas();
+ Collection tables = new LinkedHashSet();
+ for (int i = 0; i < schemas.length; i++) {
+ Table[] ts = schemas[i].getTables();
+ for (int j = 0; j < ts.length; j++)
+ tables.add(ts[j]);
+ }
+ Table[] tableArray = (Table[]) tables.toArray(new
Table[tables.size()]);
+ String[] sql = _conf.getDBDictionaryInstance()
+ .getDeleteTableContentsSQL(tableArray);
+ if (!executeSQL(sql))
+ _log.warn(_loc.get("delete-table-contents"));
+ }
+
+ /**
* Record the changes made to the DB in the current [EMAIL PROTECTED]
SchemaFactory}.
*/
public void record() {
@@ -1233,7 +1259,8 @@
* <code>false</code> to prevent writing the schema changes to the
* current [EMAIL PROTECTED] SchemaFactory}.</li>
* </ul>
- * The various actions are as follows.
+ * Actions can be composed in a comma-separated list. The various actions
+ * are as follows.
* <ul>
* <li><i>add</i>: Bring the schema up-to-date with the latest
* changes to the schema XML data by adding tables, columns,
@@ -1255,6 +1282,8 @@
* <code>file</code> option, or to stdout if no file is given.</li>
* <li><i>dropDB</i>: Execute SQL to drop the current database. This
* action implies <code>dropTables</code>.</li>
+ * <li><i>deleteTableContents</i>: Execute SQL to delete all rows from
+ * all tables that OpenJPA knows about.</li>
* <li><i>import</i>: Import the given XML schema definition into the
* current [EMAIL PROTECTED] SchemaFactory}.</li>
* <li><i>export</i>: Export the current [EMAIL PROTECTED]
SchemaFactory}'s recorded
@@ -1269,6 +1298,9 @@
* <li>Drop the current database schema:<br />
* <code>java org.apache.openjpa.jdbc.schema.SchemaTool
* -a dropDB</code></li>
+ * <li>Refresh the schema and delete all records in all tables:<br />
+ * <code>java org.apache.openjpa.jdbc.schema.SchemaTool
+ * -a refresh,deleteTableContents</code></li>
* <li>Create a schema based on an XML schema definition file:<br />
* <code>java org.apache.openjpa.jdbc.schema.SchemaTool
* myschema.xml</code></li>
@@ -1316,12 +1348,14 @@
flags.sequences = opts.removeBooleanProperty
("sequences", "sq", flags.sequences);
flags.record = opts.removeBooleanProperty("record", "r", flags.record);
- flags.action = opts.removeProperty("action", "a", flags.action);
String fileName = opts.removeProperty("file", "f", null);
String schemas = opts.removeProperty("s");
if (schemas != null)
opts.setProperty("schemas", schemas);
+ String[] actions = opts.removeProperty("action", "a", flags.action)
+ .split(",");
+
// setup a configuration instance with cmd-line info
Configurations.populateConfiguration(conf, opts);
@@ -1330,7 +1364,13 @@
getClassLoader(SchemaTool.class, null);
flags.writer = Files.getWriter(fileName, loader);
- return run(conf, args, flags, loader);
+ boolean returnValue = true;
+ for (int i = 0; i < actions.length; i++) {
+ flags.action = actions[i];
+ returnValue &= run(conf, args, flags, loader);
+ }
+
+ return returnValue;
}
/**
@@ -1370,7 +1410,8 @@
if (args.length == 0
&& !ACTION_CREATEDB.equals(flags.action)
&& !ACTION_DROPDB.equals(flags.action)
- && !ACTION_EXPORT.equals(flags.action))
+ && !ACTION_EXPORT.equals(flags.action)
+ && !ACTION_DELETE_TABLE_CONTENTS.equals(flags.action))
return false;
// parse in the arguments
Modified:
incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java
URL:
http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java?view=diff&rev=492024&r1=492023&r2=492024
==============================================================================
---
incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java
(original)
+++
incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java
Tue Jan 2 18:44:57 2007
@@ -50,6 +50,7 @@
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -1742,9 +1743,8 @@
* cases where a subselect is required and the database doesn't support
* subselects), this method should return null.
*/
- public SQLBuffer toDelete(ClassMapping mapping, Select sel,
- JDBCStore store, Object[] params) {
- return toBulkOperation(mapping, sel, store, params, null);
+ public SQLBuffer toDelete(ClassMapping mapping, Select sel, Object[]
params) {
+ return toBulkOperation(mapping, sel, null, params, null);
}
public SQLBuffer toUpdate(ClassMapping mapping, Select sel,
@@ -1893,6 +1893,47 @@
if (i.hasNext())
sql.append(", ");
}
+ }
+
+ /**
+ * Create SQL to delete the contents of the specified tables.
+ * The default implementation drops all non-deferred RESTRICT foreign key
+ * constraints involving the specified tables, issues DELETE statements
+ * against the tables, and then adds the dropped constraints back in.
+ * Databases with more optimal ways of deleting the contents of several
+ * tables should override this method.
+ */
+ public String[] getDeleteTableContentsSQL(Table[] tables) {
+ Collection sql = new ArrayList();
+
+ // collect and drop non-deferred physical restrict constraints, and
+ // collect the DELETE FROM statements
+ Collection deleteSQL = new ArrayList(tables.length);
+ Collection restrictConstraints = new LinkedHashSet();
+ for (int i = 0; i < tables.length; i++) {
+ ForeignKey[] fks = tables[i].getForeignKeys();
+ for (int j = 0; j < fks.length; j++) {
+ if (!fks[j].isLogical() && !fks[j].isDeferred()
+ && fks[j].getDeleteAction() == ForeignKey.ACTION_RESTRICT)
+ restrictConstraints.add(fks[j]);
+ String[] constraintSQL = getDropForeignKeySQL(fks[j]);
+ sql.addAll(Arrays.asList(constraintSQL));
+ }
+
+ deleteSQL.add("DELETE FROM " + tables[i].getFullName());
+ }
+
+ // add the delete statements after all the constraint mutations
+ sql.addAll(deleteSQL);
+
+ // add the deleted constraints back to the schema
+ for (Iterator iter = restrictConstraints.iterator(); iter.hasNext(); )
{
+ String[] constraintSQL =
+ getAddForeignKeySQL((ForeignKey) iter.next());
+ sql.addAll(Arrays.asList(constraintSQL));
+ }
+
+ return (String[]) sql.toArray(new String[sql.size()]);
}
/**
Modified:
incubator/openjpa/trunk/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/schema/localizer.properties
URL:
http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/schema/localizer.properties?view=diff&rev=492024&r1=492023&r2=492024
==============================================================================
---
incubator/openjpa/trunk/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/schema/localizer.properties
(original)
+++
incubator/openjpa/trunk/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/schema/localizer.properties
Tue Jan 2 18:44:57 2007
@@ -76,7 +76,7 @@
\t[-indexes/-ix <true/t | false/f>]\n\
\t[-record/-r <true/t | false/f>]\n\
\t[-action/-a <add | retain | drop | refresh | build | reflect\n\
- \t\t| createDB | dropDB | import | export>]\n\
+ \t\t| createDB | dropDB | import | export | deleteTableContents>]\n\
\t<.schema file or resource>*
sch-reflect: Reflecting on schemas "{0}". This process may take some time. \
Enable the org.apache.openjpa.jdbc.Schema logging category to see
messages about the \
@@ -142,3 +142,5 @@
generating-foreign: Reading foreign keys for table "{1}"
generating-sequences: Reading sequences for schema "{0}"
no-custom-ds: use a custom DataSource
+delete-table-contents: An error occurred while attempting to delete all \
+ records from all mapped tables.
\ No newline at end of file
Modified:
incubator/openjpa/trunk/openjpa-project/src/doc/manual/ref_guide_dbsetup.xml
URL:
http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-project/src/doc/manual/ref_guide_dbsetup.xml?view=diff&rev=492024&r1=492023&r2=492024
==============================================================================
---
incubator/openjpa/trunk/openjpa-project/src/doc/manual/ref_guide_dbsetup.xml
(original)
+++
incubator/openjpa/trunk/openjpa-project/src/doc/manual/ref_guide_dbsetup.xml
Tue Jan 2 18:44:57 2007
@@ -3626,7 +3626,8 @@
</itemizedlist>
<para>
The schema tool also accepts an <literal>-action</literal> or <literal>-a
-</literal> flag. The available actions are:
+</literal> flag. Multiple actions can be composed in a comma-separated list.
+The available actions are:
</para>
<itemizedlist>
<listitem>
@@ -3701,6 +3702,12 @@
record of the schema.
</para>
</listitem>
+ <listitem>
+ <para>
+<literal>deleteTableContents</literal>: Execute SQL to delete all rows from
+all tables that OpenJPA knows about.
+ </para>
+ </listitem>
</itemizedlist>
<note>
<para>
@@ -3749,6 +3756,34 @@
</para>
<programlisting>
java org.apache.openjpa.jdbc.schema.SchemaTool -a createDB -f script.sql
+</programlisting>
+ </example>
+ <example id="ref_guide_schema_schematool_table_cleanup">
+ <title>
+ Table Cleanup
+ </title>
+ <indexterm zone="ref_guide_schema_schematool_table_cleanup">
+ <primary>
+ schema
+ </primary>
+ <secondary>
+ refresh schema and delete all contents of all tables
+ </secondary>
+ </indexterm>
+ <indexterm zone="ref_guide_schema_schematool_table_cleanup">
+ <primary>
+ testing
+ </primary>
+ <secondary>
+ refresh schema and delete all contents of all tables
+ </secondary>
+ </indexterm>
+ <para>
+Refresh the schema and delete all contents of all tables that OpenJPA
+knows about:
+ </para>
+<programlisting>
+java org.apache.openjpa.jdbc.schema.SchemaTool -a refresh,deleteTableContents
</programlisting>
</example>
<example id="ref_guide_schema_schematool_drop">
Modified:
incubator/openjpa/trunk/openjpa-project/src/doc/manual/ref_guide_mapping.xml
URL:
http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-project/src/doc/manual/ref_guide_mapping.xml?view=diff&rev=492024&r1=492023&r2=492024
==============================================================================
---
incubator/openjpa/trunk/openjpa-project/src/doc/manual/ref_guide_mapping.xml
(original)
+++
incubator/openjpa/trunk/openjpa-project/src/doc/manual/ref_guide_mapping.xml
Tue Jan 2 18:44:57 2007
@@ -88,15 +88,16 @@
<itemizedlist>
<listitem>
<para>
-<literal>-schemaAction/-sa <add | refresh | drop | build | retain | none>
+<literal>-schemaAction/-sa <add | refresh | drop | build | retain | reflect
| createDB | dropDB | import | export | none>
</literal>: The action to take on the schema. These options correspond to the
same-named actions on the schema tool described in
-<xref linkend="ref_guide_schema_schematool"/>. Unless you are running the
-mapping tool on all of your persistent types at once or dropping a mapping, we
-strongly recommend you use the default <literal>add</literal> action or the
-<literal>build</literal> action. Otherwise you may end up inadvertently
dropping
-schema components that are used by classes you are not currently running the
-tool over.
+<xref linkend="ref_guide_schema_schematool"/>. Actions can be composed in a
+comma-separated list. Unless you are running the mapping tool on all of
+your persistent types at once or dropping a mapping, we strongly
+recommend you use the default <literal>add</literal> action or the
+<literal>build</literal> action. Otherwise you may end up inadvertently
+dropping schema components that are used by classes you are not
+currently running the tool over.
</para>
</listitem>
<listitem>
@@ -277,6 +278,22 @@
To drop the schema for a persistent class, set the mapping tool's <literal>
schemaAction</literal> to <literal>drop</literal>.
</para>
+ <example id="ref_guide_mapping_mappingtool_cleanup_tables">
+ <title>
+ Refreshing entire schema and cleaning out tables
+ </title>
+ <indexterm
zone="ref_guide_mapping_mappingtool_cleanup_tables">
+ <primary>
+ testing
+ </primary>
+ <secondary>
+ Rebuild mappings and clean tables
+ </secondary>
+ </indexterm>
+<programlisting>
+java org.apache.openjpa.jdbc.meta.MappingTool -sa add,deleteTableContents
+</programlisting>
+ </example>
<example id="ref_guide_mapping_mappingtool_dropschema">
<title>
Dropping Mappings and Association Schema