Note
We discussed the tree editor and chart editor in detail in previous articles. If you do not like to create a model with the mouse, but prefer a textual representation, you can try using Xcore . I have never used it in conjunction with EMFText, but in principle, there should be no problems.
You can also open the Ecore model using the OCLinEcore editor. It is a text, like Xcore, only OCL is used instead of Java. By the way, in a metamodel, one computed property and one control rule are written using OCLinEcore, but this is a topic for a separate article.
In a word, you have 4 editors to choose from to work with the metamodel :) Tree, diagram, text-based Java-oriented, text-based OCL-oriented.
public class User { public int id; public String name; }
CREATE TABLE "user" ( id INT CONSTRAINT user_pk PRIMARY KEY, name VARCHAR(50) NOT NULL );
CREATE TABLE "user" ( id INT CONSTRAINT user_pk PRIMARY KEY ); ALTER TABLE "user" ADD COLUMN name VARCHAR(50) NOT NULL;
Note
Probably, this does not make SQL a language with dynamic typing. I already feel like tomatoes are flying into me for “imperative and dynamically typed” SQL :) But try implementing a parser or editor of SQL scripts and you will come to the conclusion that this is equivalent to the implementation of an imperative and dynamically typed language. To determine the valid table or column names, the parser has to actually interpret the code, but without changing the real database. In this article, this interpretation is implemented as simply as possible (in the classes responsible for resolving the links described below), in reality, everything is more complicated. Some ideas on the mechanism for resolving links can be drawn from JaMoPP .
<table definition> ::= CREATE [ <table scope> ] TABLE <table name> <table contents source> [ ON COMMIT <table commit action> ROWS ]
<table scope> ::= <global or local> TEMPORARY <global or local> ::= GLOBAL | LOCAL
Note
You may get the impression that all my arguments are completely non-trivial - it all looks crazy and complicated. But everything is simpler than it seems, just try it yourself to implement some language.
<table name> ::= <local or schema qualified name> <local or schema qualified name> ::= [ <local or schema qualifier> <period> ] <qualified identifier> <local or schema qualifier> ::= <schema name> | MODULE <qualified identifier> ::= <identifier> <schema name> ::= [ <catalog name> <period> ] <unqualified schema name> <unqualified schema name> ::= <identifier> <catalog name> ::= <identifier> <identifier> ::= <actual identifier> <actual identifier> ::= <regular identifier> | <delimited identifier> <regular identifier> ::= <identifier body> <identifier body> ::= <identifier start> [ <identifier part> ... ] <identifier part> ::= <identifier start> | <identifier extend> <identifier start> ::= !! See the Syntax Rules. <identifier extend> ::= !! See the Syntax Rules. <delimited identifier> ::= <double quote> <delimited identifier body> <double quote> <delimited identifier body> ::= <delimited identifier part> ... <delimited identifier part> ::= <nondoublequote character> | <doublequote symbol> <nondoublequote character> ::= !! See the Syntax Rules. <doublequote symbol> ::= <double quote> <double quote> <double quote> ::= "
<table contents source> ::= <table element list> | OF <path-resolved user-defined type name> [ <subtable clause> ] [ <table element list> ] | <as subquery clause> <table element list> ::= <left paren> <table element> [ { <comma> <table element> }... ] <right paren> <left paren> ::= ( <right paren> ::= ) <comma> ::= ,
<table element> ::= <column definition> | <table constraint definition> | <like clause> | <self-referencing column specification> | <column options>
<column definition> ::= <column name> [ <data type> | <domain name> ] [ <reference scope check> ] [ <default clause> | <identity column specification> | <generation clause> ] [ <column constraint definition> ... ] [ <collate clause> ] <column name> ::= <identifier>
SYNTAXDEF sql FOR <http://www.emftext.org/language/sql> START Common.SQLScript
OPTIONS { reloadGeneratorModel = "true"; usePredefinedTokens = "false"; caseInsensitiveKeywords = "true"; disableBuilder = "true"; disableDebugSupport = "true"; disableLaunchSupport = "true"; disableTokenSorting = "true"; overrideProposalPostProcessor = "false"; overrideManifest = "false"; overrideUIManifest = "false"; }
TOKENS { // Default DEFINE WHITESPACE $('\u0009'|'\u000A'|'\u000B'|'\u000C'|'\u000D'|'\u0020'|'\u00A0'|'\u2000'|'\u2001'$ + $|'\u2002'|'\u2003'|'\u2004'|'\u2005'|'\u2006'|'\u2007'|'\u2008'|'\u2009'|'\u200A'$ + $|'\u200B'|'\u200C'|'\u200D'|'\u200E'|'\u200F'|'\u2028'|'\u2029'|'\u3000'|'\uFEFF')$; // Single characters DEFINE FRAGMENT SIMPLE_LATIN_LETTER $($ + SIMPLE_LATIN_UPPER_CASE_LETTER + $|$ + SIMPLE_LATIN_LOWER_CASE_LETTER + $)$; DEFINE FRAGMENT SIMPLE_LATIN_UPPER_CASE_LETTER $'A'..'Z'$; DEFINE FRAGMENT SIMPLE_LATIN_LOWER_CASE_LETTER $'a'..'z'$; DEFINE FRAGMENT DIGIT $('0'..'9')$; DEFINE FRAGMENT PLUS_SIGN $'+'$; DEFINE FRAGMENT MINUS_SIGN $'-'$; DEFINE FRAGMENT SIGN $($ + PLUS_SIGN + $|$ + MINUS_SIGN + $)$; DEFINE FRAGMENT COLON $':'$; DEFINE FRAGMENT PERIOD $'.'$; DEFINE FRAGMENT SPACE $' '$; DEFINE FRAGMENT UNDERSCORE $'_'$; DEFINE FRAGMENT SLASH $'/'$; DEFINE FRAGMENT ASTERISK $'*'$; DEFINE FRAGMENT QUOTE $'\''$; DEFINE FRAGMENT QUOTE_SYMBOL $($ + QUOTE + QUOTE + $)$; DEFINE FRAGMENT NONQUOTE_CHARACTER $~($ + QUOTE + $|$ + NEWLINE + $)$; DEFINE FRAGMENT DOUBLE_QUOTE $'"'$; DEFINE FRAGMENT DOUBLEQUOTE_SYMBOL $($ + DOUBLE_QUOTE + DOUBLE_QUOTE + $)$; DEFINE FRAGMENT NONDOUBLEQUOTE_CHARACTER $~($ + DOUBLE_QUOTE + $|$ + NEWLINE + $)$; DEFINE FRAGMENT NEWLINE $('\r\n'|'\r'|'\n')$; // Comments DEFINE SIMPLE_COMMENT SIMPLE_COMMENT_INTRODUCER + $($ + COMMENT_CHARACTER + $)*$; DEFINE FRAGMENT SIMPLE_COMMENT_INTRODUCER MINUS_SIGN + MINUS_SIGN; DEFINE FRAGMENT COMMENT_CHARACTER $~('\n'|'\r'|'\uffff')$; DEFINE BRACKETED_COMMENT BRACKETED_COMMENT_INTRODUCER + BRACKETED_COMMENT_CONTENTS + BRACKETED_COMMENT_TERMINATOR; DEFINE FRAGMENT BRACKETED_COMMENT_INTRODUCER SLASH + ASTERISK; DEFINE FRAGMENT BRACKETED_COMMENT_TERMINATOR ASTERISK + SLASH; DEFINE FRAGMENT BRACKETED_COMMENT_CONTENTS $.*$; // TODO: Nested comments // Literals DEFINE UNSIGNED_INTEGER $($ + DIGIT + $)+$; DEFINE EXACT_NUMERIC_LITERAL $($ + UNSIGNED_INTEGER + $($ + PERIOD + $($ + UNSIGNED_INTEGER + $)?)?|$ + PERIOD + UNSIGNED_INTEGER + $)$; DEFINE APPROXIMATE_NUMERIC_LITERAL MANTISSA + $'E'$ + EXPONENT; DEFINE FRAGMENT MANTISSA EXACT_NUMERIC_LITERAL; DEFINE FRAGMENT EXPONENT SIGNED_INTEGER; DEFINE FRAGMENT SIGNED_INTEGER SIGN + $?$ + UNSIGNED_INTEGER; DEFINE QUOTED_STRING QUOTE + CHARACTER_REPRESENTATION + $*$ + QUOTE; DEFINE FRAGMENT CHARACTER_REPRESENTATION $($ + NONQUOTE_CHARACTER + $|$ + QUOTE_SYMBOL + $)$; // Names and identifiers DEFINE IDENTIFIER ACTUAL_IDENTIFIER; DEFINE FRAGMENT ACTUAL_IDENTIFIER $($ + REGULAR_IDENTIFIER + $|$ + DELIMITED_IDENTIFIER + $)$; DEFINE FRAGMENT REGULAR_IDENTIFIER IDENTIFIER_BODY; DEFINE FRAGMENT IDENTIFIER_BODY IDENTIFIER_START + IDENTIFIER_PART + $*$; DEFINE FRAGMENT IDENTIFIER_PART $($ + IDENTIFIER_START + $|$ + IDENTIFIER_EXTEND + $)$; DEFINE FRAGMENT IDENTIFIER_START $('A'..'Z'|'a'..'z')$; // TODO: \p{L} - \p{M} DEFINE FRAGMENT IDENTIFIER_EXTEND $($ + DIGIT + $|$ + UNDERSCORE + $)$; // TODO: Support more characters DEFINE FRAGMENT DELIMITED_IDENTIFIER DOUBLE_QUOTE + DELIMITED_IDENTIFIER_BODY + DOUBLE_QUOTE; DEFINE FRAGMENT DELIMITED_IDENTIFIER_BODY DELIMITED_IDENTIFIER_PART + $+$; DEFINE FRAGMENT DELIMITED_IDENTIFIER_PART $($ + NONDOUBLEQUOTE_CHARACTER + $|$ + DOUBLEQUOTE_SYMBOL + $)$; }
TOKENSTYLES { "SIMPLE_COMMENT", "BRACKETED_COMMENT" COLOR #999999, ITALIC; "QUOTED_STRING" COLOR #000099, ITALIC; "EXACT_NUMERIC_LITERAL", "APPROXIMATE_NUMERIC_LITERAL", "UNSIGNED_INTEGER" COLOR #009900; }
Common.SQLScript ::= (statements !0)*; Common.SimpleComment ::= value[SIMPLE_COMMENT]; Common.BracketedComment ::= value[BRACKETED_COMMENT]; Common.SchemaQualifiedName ::= ((catalogName[IDENTIFIER] ".")? schemaName[IDENTIFIER] ".")? name[IDENTIFIER];
@SuppressWarnings(explicitSyntaxChoice) Literal.ExactNumericLiteral ::= value[EXACT_NUMERIC_LITERAL] | value[UNSIGNED_INTEGER]; Literal.ApproximateNumericLiteral ::= value[APPROXIMATE_NUMERIC_LITERAL]; Literal.CharacterStringLiteral ::= ("_" characterSetName)? values[QUOTED_STRING] (separators values[QUOTED_STRING])*; Literal.NationalCharacterStringLiteral ::= "N" values[QUOTED_STRING] (separators values[QUOTED_STRING])*; Literal.DateLiteral ::= "DATE" value[QUOTED_STRING]; Literal.TimeLiteral ::= "TIME" value[QUOTED_STRING]; Literal.TimestampLiteral ::= "TIMESTAMP" value[QUOTED_STRING]; Literal.BooleanLiteral ::= value[ "TRUE" : "FALSE" ]?;
Datatype.ExactNumericType ::= kind[ NUMERIC : "NUMERIC", DECIMAL : "DECIMAL", DEC : "DEC", SMALLINT : "SMALLINT", INTEGER : "INTEGER", INT : "INT", BIGINT : "BIGINT" ] ("(" precision[UNSIGNED_INTEGER] ("," scale[UNSIGNED_INTEGER])? ")")?; Datatype.ApproximateNumericType ::= kind[ FLOAT : "FLOAT", REAL : "REAL", DOUBLE_PRECISION : "DOUBLE PRECISION" ] ("(" precision[UNSIGNED_INTEGER] ")")?; Datatype.CharacterStringType ::= kind[ CHARACTER : "CHARACTER", CHAR : "CHAR", VARCHAR : "VARCHAR", CHARACTER_VARYING : "CHARACTER VARYING", CHAR_VARYING : "CHAR VARYING" ] ("(" length[UNSIGNED_INTEGER] ")")? ("CHARACTER" "SET" characterSetName)? ("COLLATE" collationName)?; Datatype.NationalCharacterStringType ::= kind[ NATIONAL_CHARACTER : "NATIONAL CHARACTER", NATIONAL_CHAR : "NATIONAL CHAR", NATIONAL_CHARACTER_VARYING : "NATIONAL CHARACTER VARYING", NATIONAL_CHAR_VARYING : "NATIONAL CHAR VARYING", NCHAR : "NCHAR", NCHAR_VARYING : "NCHAR VARYING" ] ("(" length[UNSIGNED_INTEGER] ")")? ("COLLATE" collationName)?; Datatype.BinaryLargeObjectStringType ::= kind[ BINARY_LARGE_OBJECT : "BINARY LARGE OBJECT", BLOB : "BLOB" ] ("(" length ")")?; Datatype.LargeObjectLength ::= value[UNSIGNED_INTEGER] multiplier[ K : "K", M : "M", G : "G" ]? units[ CHARACTERS : "CHARACTERS", CODE_UNITS : "CODE_UNITS", OCTETS : "OCTETS" ]?; Datatype.DateType ::= "DATE"; Datatype.TimeType ::= "TIME" ("(" precision[UNSIGNED_INTEGER] ")")? (withTimeZone["WITH" : "WITHOUT"] "TIME" "ZONE")?; Datatype.TimestampType ::= "TIMESTAMP" ("(" precision[UNSIGNED_INTEGER] ")")? (withTimeZone["WITH" : "WITHOUT"] "TIME" "ZONE")?; Datatype.BooleanType ::= "BOOLEAN";
Function.DatetimeValueFunction ::= kind[ CURRENT_DATE : "CURRENT_DATE", CURRENT_TIME : "CURRENT_TIME", LOCALTIME : "LOCALTIME", CURRENT_TIMESTAMP : "CURRENT_TIMESTAMP", LOCALTIMESTAMP : "LOCALTIMESTAMP" ] ("(" precision[UNSIGNED_INTEGER] ")")?; Expression.NullSpecification ::= "NULL";
Schema.TableReference ::= ((catalogName[IDENTIFIER] ".")? schemaName[IDENTIFIER] ".")? target[IDENTIFIER]; @SuppressWarnings(explicitSyntaxChoice) Schema.TableDefinition ::= "CREATE" ( scope[ PERSISTENT : "" ] | scope[ GLOBAL_TEMPORARY : "GLOBAL", LOCAL_TEMPORARY : "LOCAL" ] "TEMPORARY" ) "TABLE" schemaQualifiedName !0 contentsSource ";" !0; Schema.TableElementList ::= "(" !1 elements ("," !1 elements)* !0 ")"; Schema.Column ::= name[IDENTIFIER] dataType ("DEFAULT" defaultOption)? constraintDefinition? ("COLLATE" collationName)?; Schema.LiteralDefaultOption ::= literal; Schema.DatetimeValueFunctionDefaultOption ::= function; Schema.ImplicitlyTypedValueSpecificationDefaultOption ::= specification; Schema.NotNullColumnConstraint ::= ("CONSTRAINT" schemaQualifiedName)? "NOT" "NULL"; Schema.UniqueColumnConstraint ::= ("CONSTRAINT" schemaQualifiedName)? kind[ UNIQUE : "UNIQUE" , PRIMARY_KEY : "PRIMARY KEY" ]; Schema.ReferentialColumnConstraint ::= ("CONSTRAINT" schemaQualifiedName)? "REFERENCES" referencedTable ("(" referencedColumns[IDENTIFIER] ("," referencedColumns[IDENTIFIER])* ")")?; Schema.UniqueTableConstraint ::= ("CONSTRAINT" schemaQualifiedName)? kind[ UNIQUE : "UNIQUE" , PRIMARY_KEY : "PRIMARY KEY" ] "(" columns[IDENTIFIER] ("," columns[IDENTIFIER])* ")"; Schema.ReferentialTableConstraint ::= ("CONSTRAINT" schemaQualifiedName)? "FOREIGN" "KEY" "(" columns[IDENTIFIER] ("," columns[IDENTIFIER])* ")" "REFERENCES" referencedTable ("(" referencedColumns[IDENTIFIER] ("," referencedColumns[IDENTIFIER])* ")")?;
package org.emftext.language.sql.resource.sql.analysis; import java.util.Map; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.emftext.language.sql.resource.sql.ISqlTokenResolveResult; import org.emftext.language.sql.resource.sql.ISqlTokenResolver; public class SqlSIMPLE_COMMENTTokenResolver implements ISqlTokenResolver { public String deResolve(Object value, EStructuralFeature feature, EObject container) { return "--" + ((String) value); } public void resolve(String lexem, EStructuralFeature feature, ISqlTokenResolveResult result) { result.setResolvedToken(lexem.substring(2)); } public void setOptions(Map<?, ?> options) { } }
package org.emftext.language.sql.resource.sql.analysis; import java.util.Map; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.emftext.language.sql.resource.sql.ISqlTokenResolveResult; import org.emftext.language.sql.resource.sql.ISqlTokenResolver; public class SqlIDENTIFIERTokenResolver implements ISqlTokenResolver { public String deResolve(Object value, EStructuralFeature feature, EObject container) { return Helper.formatIdentifier((String) value); } public void resolve(String lexem, EStructuralFeature feature, ISqlTokenResolveResult result) { try { result.setResolvedToken(Helper.parseIdentifier(lexem)); } catch (Exception e) { result.setErrorMessage(e.getMessage()); } } public void setOptions(Map<?, ?> options) { } }
package org.emftext.language.sql.resource.sql.analysis; import java.util.Arrays; import java.util.HashSet; import java.util.Set; public class Helper { private static final String DOUBLE_QUOTE = "\""; private static final String DOUBLE_QUOTE_SYMBOL = "\"\""; private static final Set<String> RESERVED_WORDS = new HashSet<String>(Arrays.asList(new String[] { "ADD", "ALL", "ALLOCATE", "ALTER", "AND", "ANY", "ARE", "ARRAY", "AS", "ASENSITIVE", "ASYMMETRIC", "AT", "ATOMIC", "AUTHORIZATION", "BEGIN", "BETWEEN", "BIGINT", "BINARY", "BLOB", "BOOLEAN", "BOTH", "BY", "CALL", "CALLED", "CASCADED", "CASE", "CAST", "CHAR", "CHARACTER", "CHECK", "CLOB", "CLOSE", "COLLATE", "COLUMN", "COMMIT", "CONNECT", "CONSTRAINT", "CONTINUE", "CORRESPONDING", "CREATE", "CROSS", "CUBE", "CURRENT", "CURRENT_DATE", "CURRENT_DEFAULT_TRANSFORM_GROUP", "CURRENT_PATH", "CURRENT_ROLE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_TRANSFORM_GROUP_FOR_TYPE", "CURRENT_USER", "CURSOR", "CYCLE", "DATE", "DAY", "DEALLOCATE", "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DELETE", "DEREF", "DESCRIBE", "DETERMINISTIC", "DISCONNECT", "DISTINCT", "DOUBLE", "DROP", "DYNAMIC", "EACH", "ELEMENT", "ELSE", "END", "END-EXEC", "ESCAPE", "EXCEPT", "EXEC", "EXECUTE", "EXISTS", "EXTERNAL", "FALSE", "FETCH", "FILTER", "FLOAT", "FOR", "FOREIGN", "FREE", "FROM", "FULL", "FUNCTION", "GET", "GLOBAL", "GRANT", "GROUP", "GROUPING", "HAVING", "HOLD", "HOUR", "IDENTITY", "IMMEDIATE", "IN", "INDICATOR", "INNER", "INOUT", "INPUT", "INSENSITIVE", "INSERT", "INT", "INTEGER", "INTERSECT", "INTERVAL", "INTO", "IS", "ISOLATION", "JOIN", "LANGUAGE", "LARGE", "LATERAL", "LEADING", "LEFT", "LIKE", "LOCAL", "LOCALTIME", "LOCALTIMESTAMP", "MATCH", "MEMBER", "MERGE", "METHOD", "MINUTE", "MODIFIES", "MODULE", "MONTH", "MULTISET", "NATIONAL", "NATURAL", "NCHAR", "NCLOB", "NEW", "NO", "NONE", "NOT", "NULL", "NUMERIC", "OF", "OLD", "ON", "ONLY", "OPEN", "OR", "ORDER", "OUT", "OUTER", "OUTPUT", "OVER", "OVERLAPS", "PARAMETER", "PARTITION", "PRECISION", "PREPARE", "PRIMARY", "PROCEDURE", "RANGE", "READS", "REAL", "RECURSIVE", "REF", "REFERENCES", "REFERENCING", "REGR_AVGX", "REGR_AVGY", "REGR_COUNT", "REGR_INTERCEPT", "REGR_R2", "REGR_SLOPE", "REGR_SXX", "REGR_SXY", "REGR_SYY", "RELEASE", "RESULT", "RETURN", "RETURNS", "REVOKE", "RIGHT", "ROLLBACK", "ROLLUP", "ROW", "ROWS", "SAVEPOINT", "SCROLL", "SEARCH", "SECOND", "SELECT", "SENSITIVE", "SESSION_USER", "SET", "SIMILAR", "SMALLINT", "SOME", "SPECIFIC", "SPECIFICTYPE", "SQL", "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", "START", "STATIC", "SUBMULTISET", "SYMMETRIC", "SYSTEM", "SYSTEM_USER", "TABLE", "THEN", "TIME", "TIMESTAMP", "TIMEZONE_HOUR", "TIMEZONE_MINUTE", "TO", "TRAILING", "TRANSLATION", "TREAT", "TRIGGER", "TRUE", "UESCAPE", "UNION", "UNIQUE", "UNKNOWN", "UNNEST", "UPDATE", "UPPER", "USER", "USING", "VALUE", "VALUES", "VAR_POP", "VAR_SAMP", "VARCHAR", "VARYING", "WHEN", "WHENEVER", "WHERE", "WIDTH_BUCKET", "WINDOW", "WITH", "WITHIN", "WITHOUT", "YEAR" })); private static boolean isReservedWord(String str) { return RESERVED_WORDS.contains(str.toUpperCase()); } public static boolean isEmpty(String str) { return str == null || str.length() == 0; } public static String formatIdentifier(String str) { if (!str.matches("[AZ][A-Z0-9_]*") || isReservedWord(str)) { return DOUBLE_QUOTE + str.replace(DOUBLE_QUOTE, DOUBLE_QUOTE_SYMBOL) + DOUBLE_QUOTE; } else { return str; } } public static String parseIdentifier(String str) { if (str.startsWith(DOUBLE_QUOTE) && str.endsWith(DOUBLE_QUOTE) && str.length() >= 2) { return str.substring(1, str.length() - 1) .replace(DOUBLE_QUOTE_SYMBOL, DOUBLE_QUOTE); } else if (isReservedWord(str)) { throw new IllegalArgumentException( String.format("Reserved word %s must be quoted when used as identifier", str.toUpperCase())); } else { return str.toUpperCase(); } } }
package org.emftext.language.sql; public class UnsignedInteger { private int value; private UnsignedInteger(int value) { this.value = value; } public static UnsignedInteger valueOf(String str) { return new UnsignedInteger(Integer.parseUnsignedInt(str)); } @Override public String toString() { return String.format("%d", value); } }
Note
Eclipse, . plugin.xml , :
EDataType.Internal.ConversionDelegate.Factory.Registry.INSTANCE.put( "org.emftext.language.sql.conversionDelegateFactory", new ConversionDelegateFactory());
Note
OCLinEcore , - . . , .
package org.emftext.language.sql; import org.eclipse.emf.ecore.EDataType; import org.eclipse.emf.ecore.EDataType.Internal.ConversionDelegate; import org.eclipse.emf.ecore.EDataType.Internal.ConversionDelegate.Factory; import org.emftext.language.sql.common.CommonPackage; public class ConversionDelegateFactory implements Factory { public ConversionDelegateFactory() { } @Override public ConversionDelegate createConversionDelegate(EDataType eDataType) { if (eDataType.equals(CommonPackage.eINSTANCE.getDateType())) { return new DateConversionDelegate(); } else if (eDataType.equals(CommonPackage.eINSTANCE.getTimeType())) { return new TimeConversionDelegate(); } else if (eDataType.equals(CommonPackage.eINSTANCE.getTimestampType())) { return new TimestampConversionDelegate(); } return null; } }
package org.emftext.language.sql; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import org.eclipse.emf.ecore.EDataType.Internal.ConversionDelegate; public class TimestampConversionDelegate implements ConversionDelegate { private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder() .append(DateTimeFormatter.ISO_LOCAL_DATE) .appendLiteral(" ") .append(DateTimeFormatter.ISO_TIME) .toFormatter(); @Override public String convertToString(Object value) { ZonedDateTime timestamp = (ZonedDateTime) value; return timestamp.format(FORMATTER); } @Override public Object createFromString(String literal) { return ZonedDateTime.parse(literal, FORMATTER); } }
Note
EMF : invocationDelegates, settingDelegates, validationDelegates.
package org.emftext.language.sql.resource.sql.analysis; import java.util.Map; import org.eclipse.emf.ecore.EDataType; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.util.EcoreUtil; import org.emftext.language.sql.resource.sql.ISqlTokenResolveResult; import org.emftext.language.sql.resource.sql.ISqlTokenResolver; public class SqlQUOTED_STRINGTokenResolver implements ISqlTokenResolver { private static final String QUOTE = "'"; private static final String QUOTE_SYMBOL = "''"; public String deResolve(Object value, EStructuralFeature feature, EObject container) { String result = EcoreUtil.convertToString((EDataType) feature.getEType(), value); return QUOTE + result.replace(QUOTE, QUOTE_SYMBOL) + QUOTE; } public void resolve(String lexem, EStructuralFeature feature, ISqlTokenResolveResult result) { lexem = lexem.substring(1, lexem.length() - 1); lexem = lexem.replace(QUOTE_SYMBOL, QUOTE); try { result.setResolvedToken(EcoreUtil.createFromString((EDataType) feature.getEType(), lexem)); } catch (Exception e) { result.setErrorMessage(e.getMessage()); } } public void setOptions(Map<?, ?> options) { } }
package org.emftext.language.sql.resource.sql.analysis; import java.util.Map; import java.util.function.Consumer; import java.util.stream.Stream; import org.eclipse.emf.ecore.EReference; import org.emftext.language.sql.resource.sql.ISqlReferenceResolveResult; import org.emftext.language.sql.resource.sql.ISqlReferenceResolver; import org.emftext.language.sql.schema.Column; import org.emftext.language.sql.schema.TableColumnsConstraint; public class TableColumnsConstraintColumnsReferenceResolver implements ISqlReferenceResolver<TableColumnsConstraint, Column> { public void resolve(String identifier, TableColumnsConstraint container, EReference reference, int position, boolean resolveFuzzy, final ISqlReferenceResolveResult<Column> result) { Stream<Column> columns = container.getOwner().getElements().stream() .filter(element -> element instanceof Column) .map(col -> (Column) col); Consumer<Column> addMapping = col -> result.addMapping(col.getName(), col); if (resolveFuzzy) { columns .filter(col -> !container.getColumns().contains(col)) .filter(col -> col.getName().startsWith(identifier)) .forEach(addMapping); } else { columns .filter(col -> col.getName().equals(identifier)) .findFirst() .ifPresent(addMapping); } } public String deResolve(Column element, TableColumnsConstraint container, EReference reference) { return element.getName(); } public void setOptions(Map<?, ?> options) { } }
package org.emftext.language.sql.resource.sql.analysis; import java.util.Map; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Stream; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.util.EcoreUtil; import org.emftext.language.sql.common.SQLScript; import org.emftext.language.sql.resource.sql.ISqlReferenceResolveResult; import org.emftext.language.sql.resource.sql.ISqlReferenceResolver; import org.emftext.language.sql.schema.TableDefinition; import org.emftext.language.sql.schema.TableReference; public class TableReferenceTargetReferenceResolver implements ISqlReferenceResolver<TableReference, TableDefinition> { public void resolve(String identifier, TableReference container, EReference reference, int position, boolean resolveFuzzy, final ISqlReferenceResolveResult<TableDefinition> result) { SQLScript sqlScript = (SQLScript) EcoreUtil.getRootContainer(container); String catalogName = container.getCatalogName(); Predicate<TableDefinition> filter = !Helper.isEmpty(catalogName) ? table -> catalogName.equals(table.getSchemaQualifiedName().getCatalogName()) : table -> true; String schemaName = container.getSchemaName(); Predicate<TableDefinition> filter2 = !Helper.isEmpty(schemaName) ? table -> schemaName.equals(table.getSchemaQualifiedName().getSchemaName()) : table -> true; Stream<TableDefinition> tables = sqlScript.getStatements().stream() .filter(stmt -> stmt instanceof TableDefinition) .map(table -> (TableDefinition) table) .filter(filter.and(filter2)); Consumer<TableDefinition> addMapping = table -> result.addMapping(table.getSchemaQualifiedName().getName(), table); if (resolveFuzzy) { tables.filter(table -> table.getSchemaQualifiedName() != null && table.getSchemaQualifiedName().getName() != null && table.getSchemaQualifiedName().getName().toUpperCase().startsWith(identifier.toUpperCase())) .forEach(addMapping); } else { tables.filter(table -> table.getSchemaQualifiedName() != null && table.getSchemaQualifiedName().getName() != null && table.getSchemaQualifiedName().getName().equals(identifier)) .findFirst() .ifPresent(addMapping); } } public String deResolve(TableDefinition element, TableReference container, EReference reference) { return element.getSchemaQualifiedName().getName(); } public void setOptions(Map<?, ?> options) { } }
package org.emftext.language.sql.resource.sql.analysis; import java.util.Map; import java.util.function.Consumer; import java.util.stream.Stream; import org.eclipse.emf.ecore.EReference; import org.emftext.language.sql.resource.sql.ISqlReferenceResolveResult; import org.emftext.language.sql.resource.sql.ISqlReferenceResolver; import org.emftext.language.sql.schema.Column; import org.emftext.language.sql.schema.ReferentialConstraint; import org.emftext.language.sql.schema.TableElementList; public class ReferentialConstraintReferencedColumnsReferenceResolver implements ISqlReferenceResolver<ReferentialConstraint, Column> { public void resolve(String identifier, ReferentialConstraint container, EReference reference, int position, boolean resolveFuzzy, final ISqlReferenceResolveResult<Column> result) { Stream<Column> columns = Stream.of(container.getReferencedTable().getTarget()) .filter(table -> table != null) .map(table -> table.getContentsSource()) .filter(src -> src instanceof TableElementList) .flatMap(list -> ((TableElementList) list).getElements().stream()) .filter(element -> element instanceof Column) .map(col -> (Column) col); Consumer<Column> addMapping = col -> result.addMapping(col.getName(), col); if (resolveFuzzy) { columns .filter(col -> !container.getReferencedColumns().contains(col)) .filter(col -> col.getName().startsWith(identifier)) .forEach(addMapping); } else { columns .filter(col -> col.getName().equals(identifier)) .findFirst() .ifPresent(addMapping); } } public String deResolve(Column element, ReferentialConstraint container, EReference reference) { return element.getName(); } public void setOptions(Map<?, ?> options) { } }
package org.emftext.language.sql.resource.sql.ui; import java.util.ArrayList; import java.util.List; import java.util.function.Function; import org.eclipse.emf.ecore.EAttribute; import org.emftext.language.sql.common.SQLScript; import org.emftext.language.sql.resource.sql.analysis.Helper; import org.emftext.language.sql.schema.SchemaPackage; import org.emftext.language.sql.schema.TableDefinition; public class SqlProposalPostProcessor { public List<SqlCompletionProposal> process(List<SqlCompletionProposal> proposals) { List<SqlCompletionProposal> newProposals = new ArrayList<SqlCompletionProposal>(); EAttribute catalogNameFeature = SchemaPackage.eINSTANCE.getTableReference_CatalogName(); EAttribute schemaNameFeature = SchemaPackage.eINSTANCE.getTableReference_SchemaName(); for (SqlCompletionProposal proposal : proposals) { if (catalogNameFeature.equals(proposal.getStructuralFeature())) { addTableReferenceProposal(newProposals, proposal, table -> table.getSchemaQualifiedName().getCatalogName()); } else if (schemaNameFeature.equals(proposal.getStructuralFeature())) { addTableReferenceProposal(newProposals, proposal, table -> table.getSchemaQualifiedName().getSchemaName()); } else { newProposals.add(proposal); } } return newProposals; } private static void addTableReferenceProposal(List<SqlCompletionProposal> proposals, SqlCompletionProposal oldProposal, Function<TableDefinition, String> nameGetter) { SQLScript sqlScript = (SQLScript) oldProposal.getRoot(); String prefix = oldProposal.getPrefix().toUpperCase(); sqlScript.getStatements().stream() .filter(stmt -> stmt instanceof TableDefinition) .map(table -> (TableDefinition) table) .map(nameGetter) .filter(name -> name != null && name.length() > 0) .filter(name -> name.toUpperCase().startsWith(prefix)) .forEach(name -> proposals.add(new SqlCompletionProposal( oldProposal.getExpectedTerminal(), Helper.formatIdentifier(name), oldProposal.getPrefix(), true, oldProposal.getStructuralFeature(), oldProposal.getContainer()))); } }
Source: https://habr.com/ru/post/271945/
All Articles