Skip to content

Commit f558b98

Browse files
authored
[CDAP-21172] Add Creation/Deletion logic for Metadata Tables
[CDAP-21172] Add Creation/Deletion logic for Metadata Tables
2 parents c773de5 + e1ab57b commit f558b98

2 files changed

Lines changed: 235 additions & 36 deletions

File tree

cdap-master/pom.xml

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,6 @@
113113
<artifactId>cdap-messaging-ext-spanner</artifactId>
114114
<version>${project.version}</version>
115115
</dependency>
116-
<dependency>
117-
<groupId>io.cdap.cdap</groupId>
118-
<artifactId>cdap-metadata-ext-spanner</artifactId>
119-
<version>${project.version}</version>
120-
</dependency>
121116
<dependency>
122117
<groupId>org.apache.tephra</groupId>
123118
<artifactId>tephra-api</artifactId>
@@ -256,7 +251,6 @@
256251
<stage.authenticators.ext.dir>${stage.opt.dir}/ext/authenticators</stage.authenticators.ext.dir>
257252
<stage.credential.provider.extensions.dir>${stage.opt.dir}/ext/credentialproviders</stage.credential.provider.extensions.dir>
258253
<stage.messaging.ext.dir>${stage.opt.dir}/ext/messagingproviders</stage.messaging.ext.dir>
259-
<stage.metadata.ext.dir>${stage.opt.dir}/ext/metadatastorage</stage.metadata.ext.dir>
260254
<stage.metricswriters.ext.dir>${stage.opt.dir}/ext/metricswriters/google_cloud_monitoring_writer
261255
</stage.metricswriters.ext.dir>
262256
<stage.eventwriters.ext.dir>${stage.opt.dir}/ext/eventwriters/google_cloud_pubsub_writer
@@ -739,28 +733,6 @@
739733
</configuration>
740734
</execution>
741735

742-
<!-- Copy metadata extensions -->
743-
<execution>
744-
<id>copy-metadata-ext-spanner</id>
745-
<phase>process-resources</phase>
746-
<goals>
747-
<goal>copy-resources</goal>
748-
</goals>
749-
<configuration combine.self="override">
750-
<outputDirectory>${stage.metadata.ext.dir}/spanner</outputDirectory>
751-
<resources>
752-
<resource>
753-
<directory>
754-
${project.parent.basedir}/cdap-metadata-ext-spanner/target/libexec/
755-
</directory>
756-
<includes>
757-
<include>*.jar</include>
758-
</includes>
759-
</resource>
760-
</resources>
761-
</configuration>
762-
</execution>
763-
764736
<!-- Copy remote authenticator extensions -->
765737
<execution>
766738
<id>copy-authenticator-ext-gcp</id>
@@ -848,7 +820,7 @@
848820
<exclude name="${additional.artifacts.exclude.pattern}"/>
849821
<exclude name="**/target/*-sources.jar"/>
850822
<exclude name="**/target/*-javadoc.jar"/>
851-
<!-- Wrangler excludes -->
823+
<!-- Wrangler excludes -->
852824
<exclude name="**/target/wrangler-core*"/>
853825
<exclude name="**/target/wrangler-test*"/>
854826
<!--We don't want to package the following plugins with CDAP-->

cdap-metadata-ext-spanner/src/main/java/io/cdap/cdap/metadata/spanner/SpannerMetadataStorage.java

Lines changed: 234 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616

1717
package io.cdap.cdap.metadata.spanner;
1818

19+
import com.google.cloud.spanner.DatabaseAdminClient;
20+
import com.google.cloud.spanner.ErrorCode;
21+
import com.google.cloud.spanner.Spanner;
22+
import com.google.cloud.spanner.SpannerException;
23+
import com.google.cloud.spanner.SpannerOptions;
24+
import com.google.common.util.concurrent.Uninterruptibles;
1925
import io.cdap.cdap.spi.metadata.Metadata;
2026
import io.cdap.cdap.spi.metadata.MetadataChange;
2127
import io.cdap.cdap.spi.metadata.MetadataMutation;
@@ -25,36 +31,249 @@
2531
import io.cdap.cdap.spi.metadata.Read;
2632
import io.cdap.cdap.spi.metadata.SearchRequest;
2733
import io.cdap.cdap.spi.metadata.SearchResponse;
34+
import org.slf4j.Logger;
35+
import org.slf4j.LoggerFactory;
2836
import java.io.IOException;
37+
import java.util.ArrayList;
2938
import java.util.List;
39+
import java.util.Map;
40+
import java.util.Objects;
41+
import java.util.concurrent.ExecutionException;
3042

3143
/**
3244
* A metadata storage provider that delegates to Spanner.
3345
*/
3446
public class SpannerMetadataStorage implements MetadataStorage {
3547

48+
private static final Logger LOG = LoggerFactory.getLogger(SpannerMetadataStorage.class);
49+
50+
// Metadata table names
51+
private static final String METADATA_TABLE = "metadata";
52+
private static final String METADATA_PROPS_TABLE = "metadata_props";
53+
54+
public static final class MetadataTable {
55+
private static final String METADATA_ID_FIELD = "metadata_id";
56+
private static final String METADATA_COLUMN_FIELD = "metadata_column";
57+
private static final String NAMESPACE_FIELD = "namespace";
58+
private static final String TYPE_FIELD = "entity_type";
59+
private static final String NAME_FIELD = "name";
60+
private static final String VERSION = "version";
61+
private static final String CREATED_FIELD = "create_time";
62+
private static final String USER_FIELD = "user";
63+
private static final String SYSTEM_FIELD = "system";
64+
}
65+
66+
public static final class MetadataPropsTable {
67+
private static final String METADATA_ID_FIELD = "metadata_id";
68+
private static final String NAMESPACE_FIELD = "namespace";
69+
private static final String TYPE_FIELD = "entity_type";
70+
private static final String NESTED_NAME_FIELD = "name";
71+
private static final String NESTED_SCOPE_FIELD = "scope";
72+
private static final String NESTED_VALUE_FIELD = "value";
73+
}
74+
75+
private String instanceId;
76+
private String projectId;
77+
private String databaseId;
78+
79+
private Spanner spanner;
80+
private DatabaseAdminClient adminClient;
81+
3682
@Override
3783
public void initialize(MetadataStorageContext context) throws Exception {
38-
throw new IOException("NOT IMPLEMENTED");
84+
Map<String, String> properties = context.getProperties();
85+
86+
this.projectId = Objects.requireNonNull(properties.get("project"));
87+
this.instanceId = Objects.requireNonNull(properties.get("instance"));
88+
this.databaseId = Objects.requireNonNull(properties.get("database"));
89+
90+
this.spanner = SpannerOptions.newBuilder().setProjectId(projectId).build().getService();
91+
this.adminClient = spanner.getDatabaseAdminClient();
92+
93+
LOG.info("SpannerMetadataStorage initialized.");
3994
}
4095

4196
@Override
42-
public void close() {
97+
public String getName() {
98+
return "gcp-spanner";
4399
}
44100

45101
@Override
46102
public void createIndex() throws IOException {
47-
throw new IOException("NOT IMPLEMENTED");
103+
getCreateTableDDLStatement();
104+
LOG.info("Index creation completed.");
48105
}
49106

50-
@Override
51-
public String getName() {
52-
return "gcp-spanner";
107+
private void getCreateTableDDLStatement() throws IOException {
108+
List<String> ddlStatements = new ArrayList<>();
109+
ddlStatements.add(getCreateMetadataTableDDLStatement());
110+
ddlStatements.add(getCreateMetadataPropsTableDDLStatement());
111+
ddlStatements.addAll(getAllSearchIndexDDLStatements());
112+
executeDDLStatements(ddlStatements);
113+
}
114+
115+
/**
116+
* <p>Key features of the schema:
117+
* <ul>
118+
* <li>**`metadata_id`:** An Unique which is used to identify the various
119+
* components of the pipeline metadata.
120+
* </li>
121+
* <li>**`namespace`:** It contains the namespace associated with a given entity.
122+
* </li>
123+
* <li>**`entity_type`:** It contains the type of entity.
124+
* </li>
125+
* <li>**`name`:** Name of the entity.
126+
* </li>
127+
* <li>**`created`:** creation time of the entity.</li>
128+
* <li>**`user`:** user scope related properties and tags</li>
129+
* <li>**`system`:**system scope related properties and tags </li>
130+
* <li>**`metadata_column`:** contains all the metadata related </li>
131+
* <li>user_tokens, system_tokens and text_tokens are the Tokenlists of User,
132+
* System on which we create search indexes and use them for user, system
133+
* and null scope searches respectively.</li>
134+
* </ul>
135+
* </p>
136+
* TODO(CDAP-21174): Add the TTl policies for cleanups.
137+
*/
138+
private String getCreateMetadataTableDDLStatement() {
139+
return String.format(
140+
"CREATE TABLE IF NOT EXISTS %s (" + // metadata
141+
"%s STRING(MAX) NOT NULL," + // metadata_id
142+
"%s STRING(MAX) NOT NULL," + // namespace
143+
"%s STRING(MAX) NOT NULL," + // entity_type
144+
"%s STRING(MAX) NOT NULL," + // name
145+
"%s INT64," + // create_time
146+
"%s STRING(MAX)," + // user
147+
"%s STRING(MAX)," + // system
148+
"%s JSON," + // metadata_column
149+
"%s INT64 NOT NULL," + // version
150+
"user_tokens TOKENLIST AS " + // user_tokens list
151+
"(TOKENIZE_SUBSTRING(%s, support_relative_search=>TRUE)) HIDDEN," +
152+
"system_tokens TOKENLIST AS " + // system_tokens list
153+
"(TOKENIZE_SUBSTRING(%s, support_relative_search=>TRUE)) HIDDEN," +
154+
"text_tokens TOKENLIST AS " + // text_tokens list
155+
"(TOKENLIST_CONCAT([User_Tokens, System_Tokens])) HIDDEN," +
156+
") PRIMARY KEY (%s) ", // metadata_id
157+
METADATA_TABLE,
158+
MetadataTable.METADATA_ID_FIELD,
159+
MetadataTable.NAMESPACE_FIELD,
160+
MetadataTable.TYPE_FIELD,
161+
MetadataTable.NAME_FIELD,
162+
MetadataTable.CREATED_FIELD,
163+
MetadataTable.USER_FIELD,
164+
MetadataTable.SYSTEM_FIELD,
165+
MetadataTable.METADATA_COLUMN_FIELD,
166+
MetadataTable.VERSION,
167+
MetadataTable.USER_FIELD,
168+
MetadataTable.SYSTEM_FIELD,
169+
MetadataTable.METADATA_ID_FIELD
170+
);
171+
}
172+
173+
/**
174+
* <p>Key features of the schema:
175+
* <ul>
176+
* <li>**`metadata_id`:**An Unique which is used to identify the various
177+
* components of the pipeline metadata.
178+
* </li>
179+
* <li>**`namespace`:** It contains the namespace associated with a given entity.
180+
* </li>
181+
* <li>**`entity_type`:** It contains the type of entity.
182+
* </li>
183+
* <li>**`props_name`:** Contains the name of the properties/tags associated to an
184+
* entries in metadata table.
185+
* </li>
186+
* <li>**`props_scope`:** Contains the scope of the properties/tags associated to an
187+
* entries in metadata table.
188+
* </li>
189+
* <li>**`props_value`:** Contains the value of the properties/tags associated to an
190+
* entries in metadata table.
191+
* <li>value_tokens is the Tokenlist of Props_Value Column on which we create search
192+
* indexes and use of key:value type searches.
193+
* respectively.</li>
194+
* </ul>
195+
* </p>
196+
*/
197+
private String getCreateMetadataPropsTableDDLStatement() {
198+
return String.format(
199+
"CREATE TABLE IF NOT EXISTS %s (" +
200+
"%s STRING(MAX) NOT NULL," + // metadata_id
201+
"%s STRING(MAX) NOT NULL," + // namespace
202+
"%s STRING(MAX) NOT NULL," + // entity_type
203+
"%s STRING(MAX) NOT NULL," + // name
204+
"%s STRING(MAX)," + // scope
205+
"%s STRING(MAX)," + // value
206+
"value_tokens TOKENLIST AS " + // value_tokens list
207+
"(TOKENIZE_SUBSTRING(%s, support_relative_search=>TRUE)) HIDDEN," +
208+
") PRIMARY KEY (%s, %s, %s) ," + // metadata_id, name, scope
209+
"INTERLEAVE IN PARENT %s ON DELETE CASCADE",
210+
METADATA_PROPS_TABLE,
211+
MetadataPropsTable.METADATA_ID_FIELD,
212+
MetadataPropsTable.NAMESPACE_FIELD,
213+
MetadataPropsTable.TYPE_FIELD,
214+
MetadataPropsTable.NESTED_NAME_FIELD,
215+
MetadataPropsTable.NESTED_SCOPE_FIELD,
216+
MetadataPropsTable.NESTED_VALUE_FIELD,
217+
MetadataPropsTable.NESTED_VALUE_FIELD,
218+
MetadataPropsTable.METADATA_ID_FIELD,
219+
MetadataPropsTable.NESTED_NAME_FIELD,
220+
MetadataPropsTable.NESTED_SCOPE_FIELD,
221+
METADATA_TABLE
222+
);
223+
}
224+
225+
private List<String> getAllSearchIndexDDLStatements() {
226+
List<String> ddlStatements = new ArrayList<>();
227+
228+
// Create SEARCH INDEX on the Tokenized column(user_tokens) of user column.
229+
ddlStatements.add(String.format("CREATE SEARCH INDEX UserNgramIndex ON %s(user_tokens)", METADATA_TABLE));
230+
231+
// Creates SEARCH INDEX on the Tokenized column(system_tokens) of system column.
232+
ddlStatements.add(String.format("CREATE SEARCH INDEX SystemNgramIndex ON %s(system_tokens)", METADATA_TABLE));
233+
234+
// Creates SEARCH INDEX on the Tokenized column(text_tokens) of user and system column.
235+
ddlStatements.add(String.format("CREATE SEARCH INDEX TextNgramIndex ON %s(text_tokens)", METADATA_TABLE));
236+
237+
// Creates SEARCH INDEX on the Tokenized column(value_tokens) of props_value column.
238+
ddlStatements.add(String.format("CREATE SEARCH INDEX ValueNgramIndex ON %s(value_tokens)", METADATA_PROPS_TABLE));
239+
240+
return ddlStatements;
241+
}
242+
243+
/**
244+
* Creates the necessary metadata tables and associated search indexes in the database.
245+
* This method orchestrates the creation of the core metadata table, metadata_props table
246+
* and search indexes to facilitate efficient querying of user,system, text, and value-based
247+
* data within the metadata.
248+
* TODO (CDAP-21176): Checking additional Schema updates for schema backward compatibility.
249+
*/
250+
private void executeDDLStatements(List<String> ddlStatements) throws IOException {
251+
if (ddlStatements.isEmpty()) {
252+
LOG.debug("No ddl statements to execute");
253+
return;
254+
}
255+
try {
256+
Uninterruptibles.getUninterruptibly(
257+
adminClient.updateDatabaseDdl(instanceId,
258+
databaseId, ddlStatements, null));
259+
} catch (ExecutionException e) {
260+
Throwable cause = e.getCause();
261+
if (cause instanceof SpannerException
262+
&& ((SpannerException) cause).getErrorCode() == ErrorCode.FAILED_PRECONDITION) {
263+
LOG.debug("Concurrent statement execution error: ", e);
264+
} else {
265+
throw new IOException(e);
266+
}
267+
}
53268
}
54269

55270
@Override
56271
public void dropIndex() throws IOException {
57-
throw new IOException("NOT IMPLEMENTED");
272+
List<String> statements = new ArrayList<>();
273+
statements.add(String.format("DROP TABLE IF EXISTS %s", METADATA_TABLE));
274+
statements.add(String.format("DROP TABLE IF EXISTS %s", METADATA_PROPS_TABLE));
275+
executeDDLStatements(statements);
276+
LOG.info("Metadata Tables dropped successfully.");
58277
}
59278

60279
@Override
@@ -77,5 +296,13 @@ public Metadata read(Read read) throws IOException {
77296
public SearchResponse search(SearchRequest request) throws IOException {
78297
throw new IOException("NOT IMPLEMENTED");
79298
}
299+
300+
@Override
301+
public synchronized void close() {
302+
if (spanner != null) {
303+
spanner.close();
304+
spanner = null;
305+
}
306+
}
80307
}
81308

0 commit comments

Comments
 (0)