diff --git a/api/src/main/java/org/teiid/translator/SourceSystemFunctions.java b/api/src/main/java/org/teiid/translator/SourceSystemFunctions.java index 3239119b3f..749aea32d7 100644 --- a/api/src/main/java/org/teiid/translator/SourceSystemFunctions.java +++ b/api/src/main/java/org/teiid/translator/SourceSystemFunctions.java @@ -191,6 +191,7 @@ public class SourceSystemFunctions { public static final String ST_EQUALS = "st_equals"; //$NON-NLS-1$ public static final String ST_TRANSFORM = "st_transform"; //$NON-NLS-1$ public static final String ST_SIMPLIFY = "st_simplify"; //$NON-NLS-1$ + public static final String ST_SIMPLIFYPRESERVETOPOLOGY = "st_simplifypreservetopology"; //$NON-NLS-1$ public static final String ST_FORCE_2D = "st_force_2d"; //$NON-NLS-1$ public static final String ST_ENVELOPE = "st_envelope"; //$NON-NLS-1$ public static final String ST_WITHIN = "st_within"; //$NON-NLS-1$ @@ -237,5 +238,7 @@ public class SourceSystemFunctions { public static final String ST_X = "st_x"; //$NON-NLS-1$ public static final String ST_Y = "st_y"; //$NON-NLS-1$ public static final String ST_Z = "st_z"; //$NON-NLS-1$ + public static final String ST_MAKEENVELOPE = "st_makeenvelope"; //$NON-NLS-1$ + public static final String ST_SNAPTOGRID = "st_snaptogrid"; //$NON-NLS-1$ } diff --git a/build/kits/jboss-as7/overlay/docs/teiid/teiid-releasenotes.html b/build/kits/jboss-as7/overlay/docs/teiid/teiid-releasenotes.html index 678f3dbfdd..e593d357a3 100644 --- a/build/kits/jboss-as7/overlay/docs/teiid/teiid-releasenotes.html +++ b/build/kits/jboss-as7/overlay/docs/teiid/teiid-releasenotes.html @@ -216,8 +216,13 @@
TEIID-4205 By default, the wrapping begin/commit of a UseDeclareFetch cursor will be ignored as Teiid does not require a transaction. Set the org.teiid.honorDeclareFetchTxn system property to false to revert to the old behavior which honored the transaction.
+ +TEIID-4866 For usability +with SQLAlchemy and Superset the version() function over ODBC will report ""PostgreSQL 8.2" rather than "Teiid version". +You can use the system property org.teiid.pgVersion to control this further.
+|| | | | | | | | | | | | | - | ) + | | ) { return getToken(0); } @@ -3168,7 +3167,7 @@ Expression booleanPrimary(ParseInfo info) : LOOKAHEAD(2) ex=regexMatchCrit(info, ex) | ex=setCrit(info, ex) | ex=isNullCrit(info, ex) | - LOOKAHEAD(operator() ( | | ) subquery(info)) ex=subqueryCompareCriteria(info, ex) | + LOOKAHEAD(operator() ( | | )) ex=subqueryCompareCriteria(info, ex) | ex=compareCrit(info, ex) )] ) @@ -3270,6 +3269,7 @@ SubqueryCompareCriteria subqueryCompareCriteria(ParseInfo info, Expression expre Token operator = null; Token quantifier = null; ExistsCriteria.SubqueryHint hint = null; + Expression expr = null; } { operator=operator() @@ -3278,12 +3278,15 @@ SubqueryCompareCriteria subqueryCompareCriteria(ParseInfo info, Expression expre quantifier= ) { hint = getSubqueryHint(getToken(1)); } - subquery = subquery(info) + ( LOOKAHEAD(subquery(info)) subquery = subquery(info) | + ( expr = expression(info) ) + ) { subqueryCrit = new SubqueryCompareCriteria(); subqueryCrit.setLeftExpression(expression); subqueryCrit.setCommand(subquery); + subqueryCrit.setArrayExpression(expr); // Set operator subqueryCrit.setOperator(getOperator(operator.image)); @@ -4065,6 +4068,8 @@ Expression unsignedValueExpressionPrimary(ParseInfo info) : | expression = nestedExpression(info) | + expression = arrayExpression(info) + | // Searched CASE expressions LOOKAHEAD(2) expression = searchedCaseExpression(info) | @@ -4076,6 +4081,29 @@ Expression unsignedValueExpressionPrimary(ParseInfo info) : } } +/* +name=ARRAY expression constructor +description=Creates and array of the given expressions. +example=[source,sql]\n----\ARRAY[1,2]\n----\n +*/ +Expression arrayExpression(ParseInfo info) : +{ + Expression arrayExpression = null; + List arrayExpressions = new ArrayList (); +} +{ + ( + [arrayExpression = expression(info) {arrayExpressions.add(arrayExpression);} + ( arrayExpression = expression(info) {arrayExpressions.add(arrayExpression);})* + ] + + ) + { + return new Array(arrayExpressions); + } +} + + /* name=window specification description=The window specification for an analytical or windowed aggregate function. diff --git a/engine/src/main/resources/org/teiid/metadata/SYS.sql b/engine/src/main/resources/org/teiid/metadata/SYS.sql index 6d0446cde4..121f41a6ee 100644 --- a/engine/src/main/resources/org/teiid/metadata/SYS.sql +++ b/engine/src/main/resources/org/teiid/metadata/SYS.sql @@ -86,7 +86,9 @@ CREATE FOREIGN TABLE Keys ( IsIndexed boolean NOT NULL, RefKeyUID string(50), UID string(50) NOT NULL, - OID integer, + TableUID string(50) NOT NULL, + RefTableUID string(50) NOT NULL, + ColPositions short[] NOT NULL, PRIMARY KEY (VDBName, SchemaName, TableName, Name), FOREIGN KEY (VDBName, SchemaName, TableName) REFERENCES Tables (VDBName, SchemaName, Name), UNIQUE (UID) @@ -185,7 +187,8 @@ CREATE FOREIGN TABLE ReferenceKeyColumns ( DELETE_RULE integer, FK_NAME string(255), PK_NAME string(255), - DEFERRABILITY integer + DEFERRABILITY integer, + FK_UID string(50) ); CREATE FOREIGN TABLE Schemas ( diff --git a/engine/src/main/resources/org/teiid/query/i18n.properties b/engine/src/main/resources/org/teiid/query/i18n.properties index 28a9f8fefe..d2d5dcf311 100644 --- a/engine/src/main/resources/org/teiid/query/i18n.properties +++ b/engine/src/main/resources/org/teiid/query/i18n.properties @@ -160,7 +160,6 @@ ERR.015.012.0007 = The updatable view query has a non-updatable expression {0} f ERR.015.012.0008 = The updatable view query cannot use SELECT DISTINCT. ERR.015.012.0009 = The updatable view query has a FROM clause that is not a single table, pass-through processing will not be used for UPDATE/DELETE operations. ERR.015.012.0010 = The updatable view query does not project the column {0}, which is required to make {1} a target of INSERT operations. -ERR.015.012.0011 = There must be exactly one projected symbol in the subcommand of an IN clause. ERR.015.012.0013 = The query defining an updatable virtual group cannot use LIMIT. ERR.015.012.0014 = The non-simple query defining an updatable view does not have a valid key preserving delete target. ERR.015.012.0015 = The query defining an updatable view has no valid target for INSERTs. @@ -213,7 +212,6 @@ ERR.015.012.0062 = Elements cannot appear more than once in a SET or USING claus ERR.015.012.0063 = Multiple failures occurred during validation: ERR.015.012.0064 = Validation succeeded TEIID30124=Loop cursor or exception group name {0} already exists. -ERR.015.012.0067 = No scalar subqueries are allowed in the SELECT with no FROM clause. ERR.015.012.0069 = INTO clause can not be used in XML query. # optimizer (004) @@ -794,6 +792,11 @@ SystemSource.st_simplify_param1=Geometry SystemSource.st_simplify_param2=tolerance SystemSource.st_simplify_result=Geometry value simplified according to the Douglas Peucker algorithm +SystemSource.st_simplifypreservetopology_description=Simplify according to the Douglas Peucker algorithm, but preseve the topology and only create valid results +SystemSource.st_simplifypreservetopology_param1=Geometry +SystemSource.st_simplifypreservetopology_param2=tolerance +SystemSource.st_simplifypreservetopology_result=Geometry value simplified according to the Douglas Peucker algorithm + SystemSource.st_force_2d_description=Force the geometry to a 2d representation only having X and Y coordinates SystemSource.st_force_2d_param1=Geometry SystemSource.st_force_2d_result=2d Geometry value @@ -1018,6 +1021,35 @@ SystemSource.st_y_result=Double ordinate value SystemSource.st_z_description=If the geometry is a Point, returns the X ordinate if set SystemSource.st_z_param1=Geometry SystemSource.st_z_result=Double ordinate value + +SystemSource.st_makeenvelope_description=Make a Polygon bounding box using the given x,y values +SystemSource.st_makeenvelope_param1=xmin +SystemSource.st_makeenvelope_param2=ymin +SystemSource.st_makeenvelope_param3=xmax +SystemSource.st_makeenvelope_param4=ymax +SystemSource.st_makeenvelope_param5=srid +SystemSource.st_makeenvelope_result=Geometry representing the bounding box + +SystemSource.st_snaptogrid=Pointwise reduce the geometry to grid of the given size +SystemSource.st_snaptogrid_param1=Geometry +SystemSource.st_snaptogrid_param2=size +SystemSource.st_snaptogrid_result=The resulting geometry + +SystemSource.md5_description=Hashes a String or varbinary using the MD5 algorithm +SystemSource.md5_param1=input +SystemSource.md5_result=the varbinary hash + +SystemSource.sha1_description=Hashes a String or varbinary using the SHA1 algorithm +SystemSource.sha1_param1=input +SystemSource.sha1_result=the varbinary hash + +SystemSource.sha2_256_description=Hashes a String or varbinary using the SHA2-256 algorithm +SystemSource.sha2_256_param1=input +SystemSource.sha2_256_result=the varbinary hash + +SystemSource.sha2_512_description=Hashes a String or varbinary using the SHA2-512 algorithm +SystemSource.sha2_512_param1=input +SystemSource.sha2_512_result=the varbinary hash TEIID30350=Element ''{0}'' not found. TEIID30351=Group ''{0}'' not found. @@ -1522,3 +1554,7 @@ TEIID31206=ST_CurveToLine is not implemented in Teiid TEIID31207=Expected a LineString for the polygon shell TEIID31208=Expected a Point to get the ordinate value TEIID31209=Exceeded the maximum estimated memory footprint of {0} for the array value. +TEIID31210=The number of projected subuqery {0} columns do not match the number of row value columns {1} in {2} +TEIID31174=Result set or count exceeds the maximum integer value. + +TEIID31175=Array type expected for {0} but was {1}. diff --git a/engine/src/test/java/org/teiid/dqp/internal/process/TestDQPCore.java b/engine/src/test/java/org/teiid/dqp/internal/process/TestDQPCore.java index 754475a090..398eb61e01 100644 --- a/engine/src/test/java/org/teiid/dqp/internal/process/TestDQPCore.java +++ b/engine/src/test/java/org/teiid/dqp/internal/process/TestDQPCore.java @@ -140,6 +140,8 @@ public void run() { DQPWorkContext.setWorkContext(new DQPWorkContext()); core.stop(); } + + int id = 0; public RequestMessage exampleRequestMessage(String sql) { RequestMessage msg = new RequestMessage(sql); @@ -147,6 +149,7 @@ public RequestMessage exampleRequestMessage(String sql) { msg.setFetchSize(10); msg.setPartialResults(false); msg.setExecutionId(100); + msg.setExecutionId(id++); return msg; } @@ -329,6 +332,7 @@ public RequestMessage exampleRequestMessage(String sql) { //insensitive should not block reqMsg.setCursorType(ResultSet.TYPE_SCROLL_INSENSITIVE); + reqMsg.setExecutionId(id++); message = core.executeRequest(reqMsg.getExecutionId(), reqMsg); rm = message.get(500000, TimeUnit.MILLISECONDS); diff --git a/engine/src/test/java/org/teiid/dqp/internal/process/TestDataTierManager.java b/engine/src/test/java/org/teiid/dqp/internal/process/TestDataTierManager.java index 38de14840b..1727e2bbca 100644 --- a/engine/src/test/java/org/teiid/dqp/internal/process/TestDataTierManager.java +++ b/engine/src/test/java/org/teiid/dqp/internal/process/TestDataTierManager.java @@ -93,6 +93,8 @@ private DataTierTupleSource helpSetup(String sql, int nodeId) throws Exception { request.setSerial(serial); return new DataTierTupleSource(request, workItem, connectorManager.registerRequest(request), dtm, limit); } + + private int id; private AtomicRequestMessage helpSetupRequest(String sql, int nodeId, QueryMetadataInterface metadata) throws Exception { DQPWorkContext workContext = RealMetadataFactory.buildWorkContext(metadata, vdb); @@ -100,7 +102,7 @@ private AtomicRequestMessage helpSetupRequest(String sql, int nodeId, QueryMetad Command command = helpGetCommand(sql, metadata); RequestMessage original = new RequestMessage(); - original.setExecutionId(1); + original.setExecutionId(id++); original.setPartialResults(true); RequestID requestID = workContext.getRequestID(original.getExecutionId()); diff --git a/engine/src/test/java/org/teiid/query/parser/TestParser.java b/engine/src/test/java/org/teiid/query/parser/TestParser.java index ba987ec50b..a78f745fd0 100644 --- a/engine/src/test/java/org/teiid/query/parser/TestParser.java +++ b/engine/src/test/java/org/teiid/query/parser/TestParser.java @@ -5280,5 +5280,13 @@ private void helpTestCompoundNonJoinCriteria(String sqlPred, PredicateCriteria p query.setSelect(new Select(Arrays.asList(as))); helpTest(sql, sql, query); //$NON-NLS-1$ } + + @Test public void testUnderscoreAlias() { + String sql = "SELECT y AS _name"; + AliasSymbol as = new AliasSymbol("_name", new ElementSymbol("y")); //$NON-NLS-1$ //$NON-NLS-2$ + Query query = new Query(); + query.setSelect(new Select(Arrays.asList(as))); + helpTest(sql, "SELECT y AS \"_name\"", query); //$NON-NLS-1$ + } } diff --git a/engine/src/test/java/org/teiid/query/processor/TestArrayProcessing.java b/engine/src/test/java/org/teiid/query/processor/TestArrayProcessing.java index 94154cd77e..d2f726e228 100644 --- a/engine/src/test/java/org/teiid/query/processor/TestArrayProcessing.java +++ b/engine/src/test/java/org/teiid/query/processor/TestArrayProcessing.java @@ -37,6 +37,7 @@ import org.teiid.core.types.ArrayImpl; import org.teiid.core.types.DataTypeManager; import org.teiid.core.util.UnitTestUtil; +import org.teiid.query.metadata.TransformationMetadata; import org.teiid.query.optimizer.TestOptimizer; import org.teiid.query.optimizer.capabilities.BasicSourceCapabilities; import org.teiid.query.optimizer.capabilities.DefaultCapabilitiesFinder; @@ -203,6 +204,53 @@ public class TestArrayProcessing { assertEquals(String[][].class, command.getProjectedSymbols().get(0).getType()); } + @Test public void testQuantifiedCompareRewrite() throws Exception { + String sql = "select 'a' < ALL (('b','c'))"; //$NON-NLS-1$ + QueryResolver.resolveCommand(helpParse(sql), RealMetadataFactory.example1Cached()); + Command command = helpResolve(sql, RealMetadataFactory.example1Cached()); + assertEquals("SELECT 'a' < ALL (('b', 'c'))", command.toString()); + command = QueryRewriter.rewrite(command, RealMetadataFactory.example1Cached(), null); + assertEquals("SELECT TRUE", command.toString()); + + sql = "select 'a' < ALL ((null,'c'))"; //$NON-NLS-1$ + QueryResolver.resolveCommand(helpParse(sql), RealMetadataFactory.example1Cached()); + command = helpResolve(sql, RealMetadataFactory.example1Cached()); + command = QueryRewriter.rewrite(command, RealMetadataFactory.example1Cached(), null); + assertEquals("SELECT UNKNOWN", command.toString()); + } + + @Test(expected=QueryResolverException.class) public void testQuantifiedCompareResolving() throws Exception { + String sql = "select 'a' < ALL ('b')"; //$NON-NLS-1$ + QueryResolver.resolveCommand(helpParse(sql), RealMetadataFactory.example1Cached()); + } + + @Test(expected=QueryResolverException.class) public void testQuantifiedCompareResolving1() throws Exception { + String sql = "select 1 < ALL (('1', '2'))"; //$NON-NLS-1$ + TransformationMetadata tm = RealMetadataFactory.example1Cached().getDesignTimeMetadata(); + tm.setWidenComparisonToString(false); + QueryResolver.resolveCommand(helpParse(sql), tm); + } + + @Test public void testQuantifiedCompareResolving2() throws Exception { + String sql = "select '1' < ALL (cast(? as integer[]))"; //$NON-NLS-1$ + TransformationMetadata tm = RealMetadataFactory.example1Cached().getDesignTimeMetadata(); + tm.setWidenComparisonToString(false); + Command command = helpParse(sql); + QueryResolver.resolveCommand(command, tm); + command = QueryRewriter.rewrite(command, tm, null); + assertEquals("SELECT 1 < ALL (?)", command.toString()); + } + + @Test public void testQuantifiedCompareProcessing() throws Exception { + String sql = "select e1 from pm1.g1 where e2 = some (('a', 'b'))"; //$NON-NLS-1$ + Command command = helpParse(sql); + ProcessorPlan pp = TestProcessor.helpGetPlan(command, RealMetadataFactory.example1Cached(), TestOptimizer.getGenericFinder()); + HardcodedDataManager dataManager = new HardcodedDataManager(); + dataManager.addData("SELECT g_0.e2, g_0.e1 FROM pm1.g1 AS g_0", Arrays.asList("a", 1), Arrays.asList("c", 2)); + TestProcessor.helpProcess(pp, dataManager, new List[] {Arrays.asList(1)}); + + } + /** * TODO @Test public void testArrayLobs() { diff --git a/engine/src/test/java/org/teiid/query/processor/TestGeometry.java b/engine/src/test/java/org/teiid/query/processor/TestGeometry.java index a219539229..ab11de73ed 100644 --- a/engine/src/test/java/org/teiid/query/processor/TestGeometry.java +++ b/engine/src/test/java/org/teiid/query/processor/TestGeometry.java @@ -317,8 +317,16 @@ public void testGmlParseSrid() throws Exception { @Test public void testSimplify() throws Exception { Expression ex = TestFunctionResolving.getExpression("ST_AsText(ST_SIMPLIFY(ST_GeomFromText('LINESTRING(1 1,2 2,2 3.5,1 3,1 2,2 1)'), 1))"); assertEquals("LINESTRING (1 1, 2 3.5, 2 1)", ClobType.getString((ClobType)Evaluator.evaluate(ex))); + //ensure it creates an empty geometry + ex = TestFunctionResolving.getExpression("ST_ISEmpty(ST_Simplify(ST_GeomFromText('POLYGON((6 3,1 -2,-4 3,1 8,6 3))'),5))"); + assertTrue((Boolean)Evaluator.evaluate(ex)); } + @Test public void testSimplifyPreserveTopology() throws Exception { + Expression ex = TestFunctionResolving.getExpression("ST_AsText(ST_SimplifyPreserveTopology(ST_GeomFromText('POLYGON((6 3,1 -2,-4 3,1 8,6 3))'),5))"); + assertEquals("POLYGON ((6 3, 1 -2, -4 3, 1 8, 6 3))", ClobType.getString((ClobType)Evaluator.evaluate(ex))); + } + @Test public void testWithin() throws Exception { Expression ex = TestFunctionResolving.getExpression("ST_WITHIN(ST_GeomFromText('POINT(0 0)'), ST_GeomFromText('POINT(0 0)'))"); assertTrue((Boolean)Evaluator.evaluate(ex)); @@ -428,5 +436,15 @@ public void testGmlParseSrid() throws Exception { ex = TestFunctionResolving.getExpression("ST_AsText(ST_GeomFromGeoJson(ST_AsGeoJSON(ST_GeomFromText('LINESTRING (30 10, 10 30, 40 40)'))))"); assertEquals("LINESTRING (30 10, 10 30, 40 40)", ClobType.getString((ClobType)Evaluator.evaluate(ex))); } + + @Test public void testMakeEnvelope() throws Exception { + Expression ex = TestFunctionResolving.getExpression("ST_ASEWKT(st_makeenvelope(-1.73431370972209553,-0.71846435100548445,1.31749469692502075,1.28153564899451555,2908))"); + assertEquals("SRID=2908;POLYGON ((-1.7343137097220955 -0.7184643510054844, -1.7343137097220955 1.2815356489945156, 1.3174946969250207 1.2815356489945156, 1.3174946969250207 -0.7184643510054844, -1.7343137097220955 -0.7184643510054844))", ClobType.getString((ClobType)Evaluator.evaluate(ex))); + } + + @Test public void testSnapToGrid() throws Exception { + Expression ex = TestFunctionResolving.getExpression("ST_AsText(ST_SnapToGrid(ST_GeomFromText('LINESTRING(1.1115678 2.123, 4.111111 3.2374897, 4.11112 3.23748667)'),.001))"); + assertEquals("LINESTRING (1.112 2.123, 4.111 3.237)", ClobType.getString((ClobType)Evaluator.evaluate(ex))); + } } diff --git a/engine/src/test/java/org/teiid/query/processor/TestSetProcessing.java b/engine/src/test/java/org/teiid/query/processor/TestSetProcessing.java index a06d736cbb..ccc32e40fb 100644 --- a/engine/src/test/java/org/teiid/query/processor/TestSetProcessing.java +++ b/engine/src/test/java/org/teiid/query/processor/TestSetProcessing.java @@ -23,15 +23,17 @@ package org.teiid.query.processor; import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.junit.Test; +import org.teiid.query.metadata.TransformationMetadata; import org.teiid.query.optimizer.TestOptimizer; import org.teiid.query.optimizer.TestOptimizer.ComparisonMode; import org.teiid.query.optimizer.capabilities.DefaultCapabilitiesFinder; import org.teiid.query.unittest.RealMetadataFactory; - +@SuppressWarnings("nls") public class TestSetProcessing { @Test public void testExcept() throws Exception { @@ -114,5 +116,18 @@ public class TestSetProcessing { TestProcessor.sampleData1(manager); TestProcessor.helpProcess(plan, manager, expected); } + + @Test public void testUnionArrayNull() throws Exception { + TransformationMetadata metadata = RealMetadataFactory.fromDDL("create view v (col string[]) as select null union all select null", "x", "y"); + + ProcessorPlan plan = TestOptimizer.helpPlan("select * from v", metadata, new String[] {}); + + List>[] expected = new List[] { + Collections.singletonList(null), Collections.singletonList(null), + }; + + FakeDataManager manager = new FakeDataManager(); + TestProcessor.helpProcess(plan, manager, expected); + } } diff --git a/engine/src/test/java/org/teiid/query/resolver/TestResolver.java b/engine/src/test/java/org/teiid/query/resolver/TestResolver.java index 4fa6beacf5..6864cae83a 100644 --- a/engine/src/test/java/org/teiid/query/resolver/TestResolver.java +++ b/engine/src/test/java/org/teiid/query/resolver/TestResolver.java @@ -1215,6 +1215,13 @@ private Object[] helpGetStoredProcDefaultValues() { //test this.helpResolveException(sql); } + + @Test public void testSubQueryINArrayComparison(){ + String sql = "select e1 from pm1.g1 where (e1, e2) in (select e1, e2 from pm4.g1)"; //$NON-NLS-1$ + + //test + this.helpResolve(sql); + } @Test public void testStoredQueryInFROMSubquery() { String sql = "select X.e1 from (EXEC pm1.sq3('abc', 123)) as X"; //$NON-NLS-1$ @@ -2368,13 +2375,10 @@ private void verifyProjectedTypes(Command c, Class[] types) { helpResolveException(sql, metadata, "TEIID30095 The first three arguments for the LOOKUP function must be specified as constants."); //$NON-NLS-1$ } - /** - * We cannot implicitly convert the argument to double due to lack of precision - */ - @Test public void testPowerWithBigInteger_Fails() throws Exception { + @Test public void testPowerWithBigInteger() throws Exception { String sql = "SELECT power(10, 999999999999999999999999999999999999999999999)"; //$NON-NLS-1$ - helpResolveException(sql); + helpResolve(sql); } @Test public void testPowerWithLong() throws Exception { diff --git a/engine/src/test/java/org/teiid/query/validator/TestValidator.java b/engine/src/test/java/org/teiid/query/validator/TestValidator.java index caf317d748..f86fa928cc 100644 --- a/engine/src/test/java/org/teiid/query/validator/TestValidator.java +++ b/engine/src/test/java/org/teiid/query/validator/TestValidator.java @@ -911,6 +911,14 @@ private void helpFailProcedure(String procedure, String userUpdateStr, Table.Tri @Test public void testValidateInClauseSubquery() { helpValidate("SELECT e2 FROM test.group2 WHERE e1 IN (SELECT e1 FROM test.group)", new String[] {"e1"}, exampleMetadata()); //$NON-NLS-1$ //$NON-NLS-2$ } + + @Test public void testValidateInClauseSubqueryPasses() { + helpValidate("SELECT e2 FROM test.group2 WHERE e1 IN (SELECT e2 FROM test.group)", new String[] {}, exampleMetadata()); //$NON-NLS-1$ //$NON-NLS-2$ + } + + @Test public void testValidateInClauseSubqueryArray() { + helpValidate("SELECT e2 FROM pm1.g1 WHERE (e2, e3) IN (SELECT e2, e3 FROM pm1.g2)", new String[] {}, RealMetadataFactory.example1Cached()); //$NON-NLS-1$ //$NON-NLS-2$ + } @Test public void testValidateExec1() { helpValidate("EXEC pm1.sq1()", new String[] {}, RealMetadataFactory.example1Cached()); //$NON-NLS-1$ diff --git a/jboss-integration/kits/wildfly/docs/teiid/teiid-releasenotes.html b/jboss-integration/kits/wildfly/docs/teiid/teiid-releasenotes.html index cbafe8c003..0702cbb355 100644 --- a/jboss-integration/kits/wildfly/docs/teiid/teiid-releasenotes.html +++ b/jboss-integration/kits/wildfly/docs/teiid/teiid-releasenotes.html @@ -34,6 +34,7 @@ Highlights
TEIID-4378 TEIID-4376 Clob enhancements to support concat and use in EXECUTE IMMEDIATE. TEIID-4100 OData expand support for siblings and nested expands. TEIID-4393 Additional Geospatial Functions to more fully support the SQL-MM specification. + TEIID-4578 SQLAlchemy support through our pg/odbc access. Compatibility Issues
diff --git a/runtime/src/main/java/org/teiid/deployers/PgCatalogMetadataStore.java b/runtime/src/main/java/org/teiid/deployers/PgCatalogMetadataStore.java index 5c28560e2d..831588a554 100644 --- a/runtime/src/main/java/org/teiid/deployers/PgCatalogMetadataStore.java +++ b/runtime/src/main/java/org/teiid/deployers/PgCatalogMetadataStore.java @@ -25,17 +25,29 @@ import java.io.IOException; import java.lang.reflect.Method; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Properties; import org.teiid.adminapi.impl.SessionMetadata; import org.teiid.adminapi.impl.VDBMetaData; +import org.teiid.api.exception.query.FunctionExecutionException; +import org.teiid.api.exception.query.QueryResolverException; +import org.teiid.core.TeiidComponentException; import org.teiid.core.types.ArrayImpl; import org.teiid.core.types.BlobType; import org.teiid.core.types.ClobType; import org.teiid.core.types.DataTypeManager; +import org.teiid.core.types.GeometryType; +import org.teiid.core.util.StringUtil; +import org.teiid.language.SQLConstants; import org.teiid.metadata.Column; import org.teiid.metadata.Datatype; import org.teiid.metadata.FunctionMethod; @@ -45,13 +57,20 @@ import org.teiid.metadata.Table; import org.teiid.metadata.Table.Type; import org.teiid.odbc.ODBCServerRemoteImpl; +import org.teiid.query.function.GeometryFunctionMethods; import org.teiid.query.metadata.TransformationMetadata; +import org.teiid.query.parser.SQLParserUtil; +import org.teiid.query.resolver.util.ResolverUtil; import org.teiid.query.resolver.util.ResolverVisitor; +import org.teiid.query.sql.symbol.GroupSymbol; +import org.teiid.query.sql.visitor.SQLStringVisitor; import org.teiid.transport.PgBackendProtocol; public class PgCatalogMetadataStore extends MetadataFactory { private static final long serialVersionUID = 2158418324376966987L; + public static final String POSTGRESQL_VERSION = System.getProperties().getProperty("org.teiid.pgVersion", "PostgreSQL 8.2"); //$NON-NLS-1$ //$NON-NLS-2$ + public static final String TYPMOD = "(CASE WHEN (t1.DataType = 'bigdecimal' OR t1.DataType = 'biginteger') THEN 4+(65536*(case when (t1.Precision>32767) then 32767 else t1.Precision end)+(case when (t1.Scale>32767) then 32767 else t1.Scale end)) " + //$NON-NLS-1$ "WHEN (t1.DataType = 'string' OR t1.DataType = 'char') THEN (CASE WHEN (t1.Length <= 2147483643) THEN 4+ t1.Length ELSE 2147483647 END) ELSE -1 END)"; //$NON-NLS-1$ @@ -71,19 +90,108 @@ public PgCatalogMetadataStore(String modelName, MapdataTypes) add_matpg_relatt(); add_matpg_datatype(); add_pg_description(); - addFunction("encode", "encode").setPushdown(PushDown.CAN_PUSHDOWN);; //$NON-NLS-1$ //$NON-NLS-2$ - addFunction("postgisVersion", "PostGIS_Lib_Version"); //$NON-NLS-1$ //$NON-NLS-2$ + add_pg_prepared_xacts(); + add_pg_inherits(); + add_pg_stats(); + add_geography_columns(); + add_pg_constraint(); + addFunction("regClass", "regclass").setNullOnNull(true); //$NON-NLS-1$ //$NON-NLS-2$ + addFunction("encode", "encode").setPushdown(PushDown.CAN_PUSHDOWN); //$NON-NLS-1$ //$NON-NLS-2$ + addFunction("objDescription", "obj_description"); //$NON-NLS-1$ //$NON-NLS-2$ + addFunction("hasSchemaPrivilege", "has_schema_privilege").setNullOnNull(true); //$NON-NLS-1$ //$NON-NLS-2$ + addFunction("hasTablePrivilege", "has_table_privilege").setNullOnNull(true); //$NON-NLS-1$ //$NON-NLS-2$ + addFunction("formatType", "format_type").setNullOnNull(true); //$NON-NLS-1$ //$NON-NLS-2$ + addFunction("currentSchema", "current_schema"); //$NON-NLS-1$ //$NON-NLS-2$ + addFunction("getUserById", "pg_get_userbyid"); //$NON-NLS-1$ //$NON-NLS-2$ + addFunction("colDescription", "col_description"); //$NON-NLS-1$ //$NON-NLS-2$ + addFunction("pgHasRole", "pg_has_role"); //$NON-NLS-1$ //$NON-NLS-2$ + addFunction("asBinary2", "ST_asBinary"); //$NON-NLS-1$ //$NON-NLS-2$ + addFunction("postgisLibVersion", "PostGIS_Lib_Version"); //$NON-NLS-1$ //$NON-NLS-2$ + addFunction("postgisGeosVersion", "postgis_geos_version"); //$NON-NLS-1$ //$NON-NLS-2$ + addFunction("postgisProjVersion", "postgis_proj_version"); //$NON-NLS-1$ //$NON-NLS-2$ + addFunction("postgisVersion", "postgis_version"); //$NON-NLS-1$ //$NON-NLS-2$ addFunction("hasPerm", "has_function_privilege"); //$NON-NLS-1$ //$NON-NLS-2$ addFunction("getExpr2", "pg_get_expr"); //$NON-NLS-1$ //$NON-NLS-2$ addFunction("getExpr3", "pg_get_expr"); //$NON-NLS-1$ //$NON-NLS-2$ + addFunction("pg_table_is_visible", "pg_table_is_visible"); //$NON-NLS-1$ //$NON-NLS-2$ + addFunction("pg_get_constraintdef", "pg_get_constraintdef"); //$NON-NLS-1$ //$NON-NLS-2$ + addFunction("pg_type_is_visible", "pg_type_is_visible"); //$NON-NLS-1$ //$NON-NLS-2$ FunctionMethod func = addFunction("asPGVector", "asPGVector"); //$NON-NLS-1$ //$NON-NLS-2$ func.setProperty(ResolverVisitor.TEIID_PASS_THROUGH_TYPE, Boolean.TRUE.toString()); - addFunction("getOid", "getOid"); //$NON-NLS-1$ //$NON-NLS-2$ + addFunction("getOid", "getOid").setNullOnNull(true);; //$NON-NLS-1$ //$NON-NLS-2$ + addFunction("version", "version"); //$NON-NLS-1$ //$NON-NLS-1$ func = addFunction("pg_client_encoding", "pg_client_encoding"); //$NON-NLS-1$ //$NON-NLS-2$ func.setDeterminism(Determinism.COMMAND_DETERMINISTIC); } - private Table createView(String name) { + private Table add_pg_constraint() { + Table t = createView("pg_constraint"); //$NON-NLS-1$ + + addColumn("oid", DataTypeManager.DefaultDataTypes.INTEGER, t); //$NON-NLS-1$ + addColumn("conname", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ + addColumn("contype", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ + addColumn("consrc", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ + addColumn("conrelid", DataTypeManager.DefaultDataTypes.INTEGER, t); //$NON-NLS-1$ + addColumn("confrelid", DataTypeManager.DefaultDataTypes.INTEGER, t); //$NON-NLS-1$ + addColumn("conkey", DataTypeManager.getDataTypeName(DataTypeManager.getArrayType(DataTypeManager.DefaultDataClasses.SHORT)), t); //$NON-NLS-1$ + + String transformation = "SELECT pg_catalog.getOid(UID) as oid, name as conname, lower(left(Type, 1)) as contype, " //$NON-NLS-1$ + + "null as consrc, " //$NON-NLS-1$ + + "pg_catalog.getOid(TableUID) as conrelid, pg_catalog.getOid(RefTableUID) as confrelid, " //$NON-NLS-1$ + + "ColPositions as conkey " + //$NON-NLS-1$ + "FROM Sys.Keys WHERE Type in ('Primary', 'Unique', 'Foreign')"; //$NON-NLS-1$ + t.setSelectTransformation(transformation); + return t; + + } + + private void add_pg_prepared_xacts() { + Table t = createView("pg_prepared_xacts"); //$NON-NLS-1$ + //xid + addColumn("transaction", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ + addColumn("gid", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ + addColumn("owner", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ + addColumn("database", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ + + String transformation = "SELECT null, null, null, null from SYS.Tables WHERE 1=2"; //$NON-NLS-1$ + t.setSelectTransformation(transformation); + } + + private void add_pg_inherits() { + Table t = createView("pg_inherits"); //$NON-NLS-1$ + addColumn("inhrelid", DataTypeManager.DefaultDataTypes.INTEGER, t); //$NON-NLS-1$ + addColumn("inhparent", DataTypeManager.DefaultDataTypes.INTEGER, t); //$NON-NLS-1$ + addColumn("inhseqno", DataTypeManager.DefaultDataTypes.INTEGER, t); //$NON-NLS-1$ + + String transformation = "SELECT null, null, null from SYS.Tables WHERE 1=2"; //$NON-NLS-1$ + t.setSelectTransformation(transformation); + } + + private void add_pg_stats() { + Table t = createView("pg_stats"); //$NON-NLS-1$ + addColumn("schemaname", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ + addColumn("tablename", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ + addColumn("attname", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ + + String transformation = "SELECT null, null, null from SYS.Tables WHERE 1=2"; //$NON-NLS-1$ + t.setSelectTransformation(transformation); + } + + private void add_geography_columns() { + Table t = createView("geography_columns"); //$NON-NLS-1$ + addColumn("f_table_catalog", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ + addColumn("f_table_schema", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ + addColumn("f_table_name", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ + addColumn("f_geography_column", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ + addColumn("coord_dimension", DataTypeManager.DefaultDataTypes.INTEGER, t); //$NON-NLS-1$ + addColumn("srid", DataTypeManager.DefaultDataTypes.INTEGER, t); //$NON-NLS-1$ + addColumn("type", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ + + String transformation = "SELECT null, null, null, null, null, null, null from SYS.Tables WHERE 1=2"; //$NON-NLS-1$ + t.setSelectTransformation(transformation); + } + + private Table createView(String name) { Table t = addTable(name); t.setSystem(true); t.setSupportsUpdate(false); @@ -176,8 +284,7 @@ private Table add_pg_attribute() { "pt.oid as atttypid," + //$NON-NLS-1$ "pt.typlen as attlen, " + //$NON-NLS-1$ "convert(t1.Position, short) as attnum, " + //$NON-NLS-1$ - "(CASE WHEN (t1.DataType = 'bigdecimal' OR t1.DataType = 'biginteger') THEN (CASE WHEN (4+(cast(65536 as float)*t1.Precision)+t1.Scale) > 2147483647 THEN 2147483647 ELSE (4+(65536*t1.Precision)+t1.Scale) END) " + //$NON-NLS-1$ - "WHEN (t1.DataType = 'string' OR t1.DataType = 'char') THEN (CASE WHEN (t1.Length <= 2147483643) THEN 4+ t1.Length ELSE 2147483647 END) ELSE -1 END) as atttypmod, " + //$NON-NLS-1$ + TYPMOD +" as atttypmod, " + //$NON-NLS-1$ "CASE WHEN (t1.NullType = 'No Nulls') THEN true ELSE false END as attnotnull, " + //$NON-NLS-1$ "false as attisdropped, " + //$NON-NLS-1$ "false as atthasdef " + //$NON-NLS-1$ @@ -190,8 +297,7 @@ private Table add_pg_attribute() { "pt.oid as atttypid," + //$NON-NLS-1$ "pt.typlen as attlen, " + //$NON-NLS-1$ "convert(kc.Position, short) as attnum, " + //$NON-NLS-1$ - "(CASE WHEN (t1.DataType = 'bigdecimal' OR t1.DataType = 'biginteger') THEN (CASE WHEN (4+(cast(65536 as float)*t1.Precision)+t1.Scale) > 2147483647 THEN 2147483647 ELSE (4+(65536*t1.Precision)+t1.Scale) END) " + //$NON-NLS-1$ - "WHEN (t1.DataType = 'string' OR t1.DataType = 'char') THEN (CASE WHEN (t1.Length <= 2147483643) THEN 4+ t1.Length ELSE 2147483647 END) ELSE -1 END) as atttypmod, " + //$NON-NLS-1$ + TYPMOD +" as atttypmod, " + //$NON-NLS-1$ "CASE WHEN (t1.NullType = 'No Nulls') THEN true ELSE false END as attnotnull, " + //$NON-NLS-1$ "false as attisdropped, " + //$NON-NLS-1$ "false as atthasdef " + //$NON-NLS-1$ @@ -218,6 +324,8 @@ private Table add_pg_class() { // r = ordinary table, i = index, S = sequence, v = view, c = composite type, t = TOAST table addColumn("relkind", DataTypeManager.DefaultDataTypes.CHAR, t); //$NON-NLS-1$ + addColumn("relowner", DataTypeManager.DefaultDataTypes.INTEGER, t); //$NON-NLS-1$ + // If this is an index, the access method used (B-tree, hash, etc.) addColumn("relam", DataTypeManager.DefaultDataTypes.INTEGER, t); //$NON-NLS-1$ @@ -233,26 +341,37 @@ private Table add_pg_class() { addColumn("relhasrules", DataTypeManager.DefaultDataTypes.BOOLEAN, t); //$NON-NLS-1$ // True if we generate an OID for each row of the relation - addColumn("relhasoids", DataTypeManager.DefaultDataTypes.BOOLEAN, t); //$NON-NLS-1$ + addColumn("relhasoids", DataTypeManager.DefaultDataTypes.BOOLEAN, t); //$NON-NLS-1$ + + //additional column not present in pg metadata - for column metadata query + addColumn("relnspname", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ + + addColumn("reloptions", DataTypeManager.getDataTypeName(DataTypeManager.getArrayType(DataTypeManager.DefaultDataClasses.STRING)), t); //$NON-NLS-1$ addPrimaryKey("pk_pg_class", Arrays.asList("oid"), t); //$NON-NLS-1$ //$NON-NLS-2$ String transformation = "SELECT pg_catalog.getOid(t1.uid) as oid, t1.name as relname, " + //$NON-NLS-1$ "(SELECT pg_catalog.getOid(uid) FROM SYS.Schemas WHERE Name = t1.SchemaName) as relnamespace, " + //$NON-NLS-1$ "convert((CASE t1.isPhysical WHEN true THEN 'r' ELSE 'v' END), char) as relkind," + //$NON-NLS-1$ + "0 as relowner, " + //$NON-NLS-1$ "0 as relam, " + //$NON-NLS-1$ "convert(0, float) as reltuples, " + //$NON-NLS-1$ "0 as relpages, " + //$NON-NLS-1$ "false as relhasrules, " + //$NON-NLS-1$ - "false as relhasoids " + //$NON-NLS-1$ + "false as relhasoids, " + //$NON-NLS-1$ + "t1.SchemaName as relnspname, " + //$NON-NLS-1$ + "null as reloptions " + //$NON-NLS-1$ "FROM SYS.Tables t1 UNION ALL SELECT pg_catalog.getOid(t1.uid) as oid, t1.name as relname, " + //$NON-NLS-1$ "(SELECT pg_catalog.getOid(uid) FROM SYS.Schemas WHERE Name = t1.SchemaName) as relnamespace, " + //$NON-NLS-1$ "convert('i', char) as relkind," + //$NON-NLS-1$ + "0 as relowner, " + //$NON-NLS-1$ "0 as relam, " + //$NON-NLS-1$ "convert(0, float) as reltuples, " + //$NON-NLS-1$ "0 as relpages, " + //$NON-NLS-1$ "false as relhasrules, " + //$NON-NLS-1$ - "false as relhasoids " + //$NON-NLS-1$ + "false as relhasoids, " + //$NON-NLS-1$ + "t1.SchemaName as relnspname, " + //$NON-NLS-1$ + "null as reloptions " + //$NON-NLS-1$ "FROM SYS.Keys t1 WHERE t1.type in ('Primary', 'Unique', 'Index')"; //$NON-NLS-1$ t.setSelectTransformation(transformation); t.setMaterialized(true); @@ -311,8 +430,9 @@ private Table add_pg_namespace() { Table t = createView("pg_namespace"); //$NON-NLS-1$ addColumn("oid", DataTypeManager.DefaultDataTypes.INTEGER, t); //$NON-NLS-1$ addColumn("nspname", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ + addColumn("nspowner", DataTypeManager.DefaultDataTypes.INTEGER, t); //$NON-NLS-1$ - String transformation = "SELECT pg_catalog.getOid(uid) as oid, t1.Name as nspname " + //$NON-NLS-1$ + String transformation = "SELECT pg_catalog.getOid(uid) as oid, t1.Name as nspname, 0 as nspowner " + //$NON-NLS-1$ "FROM SYS.Schemas as t1"; //$NON-NLS-1$ t.setSelectTransformation(transformation); @@ -440,50 +560,58 @@ private Table add_pg_type() { addColumn("typrelid", DataTypeManager.DefaultDataTypes.INTEGER, t); //$NON-NLS-1$ addColumn("typelem", DataTypeManager.DefaultDataTypes.INTEGER, t); //$NON-NLS-1$ addColumn("typinput", DataTypeManager.DefaultDataTypes.INTEGER, t); //$NON-NLS-1$ + addColumn("typdefault", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ + + //non-pg column to associate the teiid type name - this is expected to be unique. + //aliases are handled by matpg_datatype + addColumn("teiid_name", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ + String transformation = - "select oid, typname, (SELECT pg_catalog.getOid(uid) FROM SYS.Schemas where Name = 'SYS') as typnamespace, typlen, typtype, false as typnotnull, typbasetype, typtypmod, cast(',' as char) as typdelim, typrelid, typelem, null as typeinput from texttable('" + //$NON-NLS-1$ - "16,boolean,1,b,0,-1,0,0\n" + //$NON-NLS-1$ - "17,varbinary,-1,b,0,-1,0,0\n" + //$NON-NLS-1$ - "1043,string,-1,b,0,-1,0,0\n" + //$NON-NLS-1$ - "25,text,-1,b,0,-1,0,0\n" + //$NON-NLS-1$ - "1042,char,1,b,0,-1,0,0\n" + //$NON-NLS-1$ - "21,short,2,b,0,-1,0,0\n" + //$NON-NLS-1$ - "20,long,8,b,0,-1,0,0\n" + //$NON-NLS-1$ - "23,integer,4,b,0,-1,0,0\n" + //$NON-NLS-1$ - "26,oid,4,b,0,-1,0,0\n" + //$NON-NLS-1$ - "700,float,4,b,0,-1,0,0\n" + //$NON-NLS-1$ - "701,double,8,b,0,-1,0,0\n" + //$NON-NLS-1$ - "705,unknown,-2,b,0,-1,0,0\n" + //$NON-NLS-1$ - "1082,date,4,b,0,-1,0,0\n" + //$NON-NLS-1$ - "1083,datetime,8,b,0,-1,0,0\n" + //$NON-NLS-1$ - "1114,timestamp,8,b,0,-1,0,0\n" + //$NON-NLS-1$ - "1700,decimal,-1,b,0,-1,0,0\n" + //$NON-NLS-1$ - "142,xml,-1,b,0,-1,0,0\n" + //$NON-NLS-1$ - "14939,lo,-1,b,0,-1,0,0\n" + //$NON-NLS-1$ - "32816,geometry,-1,b,0,-1,0,0\n" + //$NON-NLS-1$ - "2278,void,4,p,0,-1,0,0\n" + //$NON-NLS-1$ - "2249,record,-1,p,0,-1,0,0\n" + //$NON-NLS-1$ - "30,oidvector,-1,b,0,-1,0,26\n" + //$NON-NLS-1$ - "1000,_bool,-1,b,0,-1,0,16\n" + //$NON-NLS-1$ - "1001,_bytea,-1,b,0,-1,0,17\n" + //$NON-NLS-1$ - "1002,_char,-1,b,0,-1,0,18\n" + //$NON-NLS-1$ - "1005,_int2,-1,b,0,-1,0,21\n" + //$NON-NLS-1$ - "1007,_int4,-1,b,0,-1,0,23\n" + //$NON-NLS-1$ - "1009,_text,-1,b,0,-1,0,25\n" + //$NON-NLS-1$ - "1028,_oid,-1,b,0,-1,0,26\n" + //$NON-NLS-1$ - "1014,_bpchar,-1,b,0,-1,0,1042\n" + //$NON-NLS-1$ - "1015,_varchar,-1,b,0,-1,0,1043\n" + //$NON-NLS-1$ - "1016,_int8,-1,b,0,-1,0,20\n" + //$NON-NLS-1$ - "1021,_float4,-1,b,0,-1,0,700\n" + //$NON-NLS-1$ - "1022,_float8,-1,b,0,-1,0,701\n" + //$NON-NLS-1$ - "1115,_timestamp,-1,b,0,-1,0,1114\n" + //$NON-NLS-1$ - "1182,_date,-1,b,0,-1,0,1082\n" + //$NON-NLS-1$ - "1183,_time,-1,b,0,-1,0,1083\n" + //$NON-NLS-1$ - "32824,_geometry,-1,b,0,-1,0,32816\n" + //$NON-NLS-1$ - "2287,_record,-1,b,0,-1,0,2249\n" + //$NON-NLS-1$ - "2283,anyelement,4,p,0,-1,0,0\n" + //$NON-NLS-1$ - "22,int2vector,-1,b,0,-1,0,0" + //$NON-NLS-1$ - "' columns oid integer, typname string, typlen short, typtype char, typbasetype integer, typtypmod integer, typrelid integer, typelem integer) AS t"; //$NON-NLS-1$ + "select oid, typname, (SELECT pg_catalog.getOid(uid) FROM SYS.Schemas where Name = 'SYS') as typnamespace, typlen, typtype, false as typnotnull, typbasetype, typtypmod, cast(',' as char) as typdelim, typrelid, typelem, null as typeinput, null as typdefault, teiid_name from texttable('" + //$NON-NLS-1$ + "16,bool,1,b,0,-1,0,0,boolean\n" + //$NON-NLS-1$ + "17,bytea,-1,b,0,-1,0,0,blob\n" + //$NON-NLS-1$ + "1043,varchar,-1,b,0,-1,0,0,string\n" + //$NON-NLS-1$ + "25,text,-1,b,0,-1,0,0,clob\n" + //$NON-NLS-1$ + "1042,char,1,b,0,-1,0,0,\n" + //$NON-NLS-1$ + "21,int2,2,b,0,-1,0,0,short\n" + //$NON-NLS-1$ + "20,int8,8,b,0,-1,0,0,long\n" + //$NON-NLS-1$ + "23,int4,4,b,0,-1,0,0,integer\n" + //$NON-NLS-1$ + "26,oid,4,b,0,-1,0,0,\n" + //$NON-NLS-1$ + "700,float4,4,b,0,-1,0,0,float\n" + //$NON-NLS-1$ + "701,float8,8,b,0,-1,0,0,double\n" + //$NON-NLS-1$ + "705,unknown,-2,b,0,-1,0,0,object\n" + //$NON-NLS-1$ + "1082,date,4,b,0,-1,0,0,date\n" + //$NON-NLS-1$ + "1083,time,8,b,0,-1,0,0,time\n" + //$NON-NLS-1$ + "1114,timestamp,8,b,0,-1,0,0,timestamp\n" + //$NON-NLS-1$ + "1700,numeric,-1,b,0,-1,0,0,bigdecimal\n" + //$NON-NLS-1$ + "142,xml,-1,b,0,-1,0,0,xml\n" + //$NON-NLS-1$ + "14939,lo,-1,b,0,-1,0,0,\n" + //$NON-NLS-1$ + "32816,geometry,-1,b,0,-1,0,0,geometry\n" + //$NON-NLS-1$ + "2278,void,4,p,0,-1,0,0,\n" + //$NON-NLS-1$ + "2249,record,-1,p,0,-1,0,0,\n" + //$NON-NLS-1$ + "30,oidvector,-1,b,0,-1,0,26,\n" + //$NON-NLS-1$ + "1000,_bool,-1,b,0,-1,0,16,boolean[]\n" + //$NON-NLS-1$ + "1001,_bytea,-1,b,0,-1,0,17,blob[]\n" + //$NON-NLS-1$ + "1002,_char,-1,b,0,-1,0,18,\n" + //$NON-NLS-1$ + "1005,_int2,-1,b,0,-1,0,21,short[]\n" + //$NON-NLS-1$ + "1007,_int4,-1,b,0,-1,0,23,integer[]\n" + //$NON-NLS-1$ + "1009,_text,-1,b,0,-1,0,25,clob[]\n" + //$NON-NLS-1$ + "1028,_oid,-1,b,0,-1,0,26,\n" + //$NON-NLS-1$ + "1014,_bpchar,-1,b,0,-1,0,1042,\n" + //$NON-NLS-1$ + "1015,_varchar,-1,b,0,-1,0,1043,string[]\n" + //$NON-NLS-1$ + "1016,_int8,-1,b,0,-1,0,20,long[]\n" + //$NON-NLS-1$ + "1021,_float4,-1,b,0,-1,0,700,float[]\n" + //$NON-NLS-1$ + "1022,_float8,-1,b,0,-1,0,701,double[]\n" + //$NON-NLS-1$ + "1031,_numeric,-1,b,0,-1,0,1700,bigdecimal[]\n" + //$NON-NLS-1$ + "1115,_timestamp,-1,b,0,-1,0,1114,timestamp[]\n" + //$NON-NLS-1$ + "1182,_date,-1,b,0,-1,0,1082,date[]\n" + //$NON-NLS-1$ + "1183,_time,-1,b,0,-1,0,1083,time[]\n" + //$NON-NLS-1$ + "32824,_geometry,-1,b,0,-1,0,32816,geometry[]\n" + //$NON-NLS-1$ + "143,_xml,-1,b,0,-1,0,142,xml[]\n" + //$NON-NLS-1$ + "2287,_record,-1,b,0,-1,0,2249,\n" + //$NON-NLS-1$ + "2283,anyelement,4,p,0,-1,0,0,\n" + //$NON-NLS-1$ + "22,int2vector,-1,b,0,-1,0,0," + //$NON-NLS-1$ + "' columns oid integer, typname string, typlen short, typtype char, typbasetype integer, typtypmod integer, typrelid integer, typelem integer, teiid_name string) AS t"; //$NON-NLS-1$ t.setSelectTransformation(transformation); t.setMaterialized(true); return t; @@ -556,19 +684,22 @@ private Table add_matpg_datatype() { addColumn("oid", DataTypeManager.DefaultDataTypes.INTEGER, t); //$NON-NLS-1$ addColumn("typname", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ addColumn("name", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ - addColumn("uid", DataTypeManager.DefaultDataTypes.STRING, t); //$NON-NLS-1$ addColumn("typlen", DataTypeManager.DefaultDataTypes.SHORT, t); //$NON-NLS-1$ + addColumn("typtype", DataTypeManager.DefaultDataTypes.CHAR, t); //$NON-NLS-1$ + addColumn("typbasetype", DataTypeManager.DefaultDataTypes.INTEGER, t); //$NON-NLS-1$ + addColumn("typtypmod", DataTypeManager.DefaultDataTypes.INTEGER, t); //$NON-NLS-1$ addPrimaryKey("matpg_datatype_names", Arrays.asList("oid", "name"), t); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ addIndex("matpg_datatype_ids", true, Arrays.asList("typname", "oid"), t); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - String transformation = "select pt.oid as oid, pt.typname as typname, t.Name name, t.UID, pt.typlen from pg_catalog.pg_type pt JOIN (select (CASE "+//$NON-NLS-1$ - "WHEN (Name = 'clob' OR Name = 'blob') THEN 'lo' " +//$NON-NLS-1$ - "WHEN (Name = 'byte' ) THEN 'short' " +//$NON-NLS-1$ - "WHEN (Name = 'time' ) THEN 'datetime' " + //$NON-NLS-1$ - "WHEN (Name = 'biginteger' ) THEN 'decimal' " +//$NON-NLS-1$ - "WHEN (Name = 'bigdecimal' ) THEN 'decimal' " +//$NON-NLS-1$ - "WHEN (Name = 'object' ) THEN 'unknown' " +//$NON-NLS-1$ - "ELSE Name END) as pg_name, Name, UID from SYS.DataTypes) as t ON t.pg_name = pt.typname"; //$NON-NLS-1$ + String transformation = "select pt.oid as oid, pt.typname as typname, pt.teiid_name as name, pt.typlen, pt.typtype, pt.typbasetype, pt.typtypmod from pg_catalog.pg_type pt" //$NON-NLS-1$ + + " UNION ALL select pt.oid as oid, pt.typname as typname, 'char' as name, pt.typlen, pt.typtype, pt.typbasetype, pt.typtypmod from pg_catalog.pg_type pt where typname='varchar'" //$NON-NLS-1$ + + " UNION ALL select pt.oid as oid, pt.typname as typname, 'byte' as name, pt.typlen, pt.typtype, pt.typbasetype, pt.typtypmod from pg_catalog.pg_type pt where typname='int2'" //$NON-NLS-1$ + + " UNION ALL select pt.oid as oid, pt.typname as typname, 'biginteger' as name, pt.typlen, pt.typtype, pt.typbasetype, pt.typtypmod from pg_catalog.pg_type pt where typname='numeric'" //$NON-NLS-1$ + + " UNION ALL select pt.oid as oid, pt.typname as typname, 'varbinary' as name, pt.typlen, pt.typtype, pt.typbasetype, pt.typtypmod from pg_catalog.pg_type pt where typname='bytea'" //$NON-NLS-1$ + + " UNION ALL select pt.oid as oid, pt.typname as typname, 'byte[]' as name, pt.typlen, pt.typtype, pt.typbasetype, pt.typtypmod from pg_catalog.pg_type pt where typname='_int2'" //$NON-NLS-1$ + + " UNION ALL select pt.oid as oid, pt.typname as typname, 'biginteger[]' as name, pt.typlen, pt.typtype, pt.typbasetype, pt.typtypmod from pg_catalog.pg_type pt where typname='_numeric'" //$NON-NLS-1$ + + " UNION ALL select pt.oid as oid, pt.typname as typname, 'varbinary[]' as name, pt.typlen, pt.typtype, pt.typbasetype, pt.typtypmod from pg_catalog.pg_type pt where typname='_bytea'" //$NON-NLS-1$ + + " UNION ALL select pt.oid as oid, pt.typname as typname, 'char[]' as name, pt.typlen, pt.typtype, pt.typbasetype, pt.typtypmod from pg_catalog.pg_type pt where typname='_varchar'"; //$NON-NLS-1$ t.setSelectTransformation(transformation); t.setMaterialized(true); return t; @@ -588,15 +719,28 @@ private FunctionMethod addFunction(String javaFunction, String name) { throw new AssertionError("Could not find function"); //$NON-NLS-1$ } + //TODO use the TeiidFunction annotation instead public static class FunctionMethods { public static ClobType encode(BlobType value, String encoding) throws SQLException, IOException { return org.teiid.query.function.FunctionMethods.toChars(value, encoding); } + public static String postgisLibVersion() { + return "2.0.0 USE_GEOS=0 USE_PROJ=1 USE_STATS=0"; //$NON-NLS-1$ + } + public static String postgisVersion() { - return "1.4.0"; //$NON-NLS-1$ + return "2.0.0"; //$NON-NLS-1$ + } + + public static String postgisGeosVersion() { + return null; } + public static String postgisProjVersion() { + return "Rel. 4.8.0"; //$NON-NLS-1$ + } + public static Boolean hasPerm(@SuppressWarnings("unused") Integer oid, @SuppressWarnings("unused") String permission) { return true; @@ -638,5 +782,139 @@ public static String pg_client_encoding(org.teiid.CommandContext cc) { } return encoding; } + + public static Integer regClass(org.teiid.CommandContext cc, String name) throws TeiidComponentException, QueryResolverException { + VDBMetaData metadata = (VDBMetaData) cc.getVdb(); + TransformationMetadata tm = metadata.getAttachment(TransformationMetadata.class); + GroupSymbol symbol = new GroupSymbol(SQLParserUtil.normalizeId(name)); + ResolverUtil.resolveGroup(symbol, tm); + return tm.getMetadataStore().getOid(((Table)symbol.getMetadataID()).getUUID()); + } + + public static String objDescription(org.teiid.CommandContext cc, int oid) { + //TODO need a reverse lookup by oid at least for schema or add the annotation to pg_namespace + return null; + } + + public static String getUserById(int user) { + return "pgadmin"; //$NON-NLS-1$ + } + + public static boolean hasSchemaPrivilege(org.teiid.CommandContext cc, String name, String privilege) { + //TODO: could check if the schema exists + return "usage".equalsIgnoreCase(privilege); //$NON-NLS-1$ + } + + public static boolean hasTablePrivilege(org.teiid.CommandContext cc, String name, String privilege) { + //TODO: check against authorizationvalidator + return true; + } + + public static String currentSchema(org.teiid.CommandContext cc) { + return "SYS"; //$NON-NLS-1$ + } + + private static Map TypeNameMap = new HashMap (); + static { + TypeNameMap.put("bool", "boolean"); + TypeNameMap.put("varchar", "character varying"); + TypeNameMap.put("int2", "smallint"); + TypeNameMap.put("int4", "integer"); + TypeNameMap.put("int8", "bigint"); + TypeNameMap.put("float4", "real"); + TypeNameMap.put("float8", "double precision"); + } + + public static String formatType(org.teiid.CommandContext cc, int oid, int typmod) throws SQLException { + Connection c = cc.getConnection(); + try { + PreparedStatement ps = c.prepareStatement("select typname from pg_catalog.pg_type where oid = ?"); //$NON-NLS-1$ + ps.setInt(1, oid); + ps.execute(); + ResultSet rs = ps.getResultSet(); + if (rs.next()) { + String name = rs.getString(1); + boolean isArray = name.startsWith("_"); //$NON-NLS-1$ + if (isArray) { + name = name.substring(1); + } + String otherName = TypeNameMap.get(name); + if (otherName != null) { + name = otherName; + } + if (typmod > 4) { + if (name.equals("numeric")) { //$NON-NLS-1$ + name += "("+((typmod-4)>>16)+","+((typmod-4)&0xffff)+")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } else if (name.equals("bpchar") || name.equals("varchar")) { //$NON-NLS-1$ //$NON-NLS-2$ + name += "("+(typmod-4)+")"; //$NON-NLS-1$ //$NON-NLS-2$ + } + } + if (isArray) { + name += "[]"; //$NON-NLS-1$ + } + return name; + } + return "???"; //$NON-NLS-1$ + } finally { + if (c != null) { + c.close(); + } + } + } + + public static String colDescription(org.teiid.CommandContext cc, int oid, int column_number) { + //TODO need a reverse lookup by oid or add the annotation to pg_attribute + return null; + } + + public static boolean pgHasRole(org.teiid.CommandContext cc, int userOid, String privilege) { + return true; + } + + public static BlobType asBinary2(GeometryType geom, String encoding) throws FunctionExecutionException { + return GeometryFunctionMethods.asBlob(geom, encoding); + } + + public static boolean pg_table_is_visible(int oid) throws FunctionExecutionException { + return true; + } + + public static String pg_get_constraintdef(org.teiid.CommandContext cc, int oid, boolean pretty) throws SQLException { + //return a simple constraint def + Connection c = cc.getConnection(); + try { + PreparedStatement ps = c.prepareStatement("select pkcolumn_name, pktable_schem, pktable_name, fkcolumn_name from REFERENCEKEYCOLUMNS where getoid(fk_uid) = ? order by KEY_SEQ"); //$NON-NLS-1$ + ps.setInt(1, oid); + ps.execute(); + ResultSet rs = ps.getResultSet(); + String refTable = null; + List columnNames = new ArrayList (); + List refColumnNames = new ArrayList (); + while (rs.next()) { + if (refTable == null) { + refTable = SQLStringVisitor.escapeSinglePart(rs.getString(2)) + SQLConstants.Tokens.DOT + SQLStringVisitor.escapeSinglePart(rs.getString(3)); + } + columnNames.add(SQLStringVisitor.escapeSinglePart(rs.getString(4))); + refColumnNames.add(SQLStringVisitor.escapeSinglePart(rs.getString(1))); + } + if (refTable == null) { + return null; + } + return "FOREIGN KEY (" + StringUtil.join(columnNames, ",")+ ") REFERENCES " + refTable + "("+ StringUtil.join(refColumnNames, ",") + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ + } finally { + if (c != null) { + c.close(); + } + } + } + + public static boolean pg_type_is_visible(int oid) throws FunctionExecutionException { + return true; + } + + public static String version() { + return POSTGRESQL_VERSION; + } + } } diff --git a/runtime/src/main/java/org/teiid/odbc/ODBCClientRemote.java b/runtime/src/main/java/org/teiid/odbc/ODBCClientRemote.java index 026abd3974..3652ef97c8 100644 --- a/runtime/src/main/java/org/teiid/odbc/ODBCClientRemote.java +++ b/runtime/src/main/java/org/teiid/odbc/ODBCClientRemote.java @@ -75,11 +75,11 @@ enum CursorDirection { // RowDescription (B) // NoData (B) - void sendResultSetDescription(List cols); + void sendResultSetDescription(List cols, short[] resultColumnFormat); // DataRow (B) // CommandComplete (B) - void sendResults(String sql, ResultSetImpl rs, List cols, ResultsFuture result, CursorDirection direction, int rowCount, boolean describeRows); + void sendResults(String sql, ResultSetImpl rs, List cols, ResultsFuture result, CursorDirection direction, int rowCount, boolean describeRows, short[] resultColumnFormat); void sendCommandComplete(String sql, Integer count); diff --git a/runtime/src/main/java/org/teiid/odbc/ODBCServerRemote.java b/runtime/src/main/java/org/teiid/odbc/ODBCServerRemote.java index 92edcabf4e..6904d5e63a 100644 --- a/runtime/src/main/java/org/teiid/odbc/ODBCServerRemote.java +++ b/runtime/src/main/java/org/teiid/odbc/ODBCServerRemote.java @@ -22,6 +22,7 @@ package org.teiid.odbc; import java.net.SocketAddress; +import java.nio.charset.Charset; import java.util.Properties; import org.teiid.transport.PgFrontendProtocol.NullTerminatedStringDataInputStream; @@ -34,7 +35,7 @@ public interface ODBCServerRemote { void prepare(String prepareName, String sql, int[] paramType); - void bindParameters(String bindName, String prepareName, Object[] paramdata, int resultCodeCount, int[] resultColumnFormat); + void bindParameters(String bindName, String prepareName, Object[] paramdata, int resultCodeCount, short[] resultColumnFormat, Charset charset); void execute(String bindName, int maxrows); diff --git a/runtime/src/main/java/org/teiid/odbc/ODBCServerRemoteImpl.java b/runtime/src/main/java/org/teiid/odbc/ODBCServerRemoteImpl.java index 68e74e08de..6268dfb2ff 100644 --- a/runtime/src/main/java/org/teiid/odbc/ODBCServerRemoteImpl.java +++ b/runtime/src/main/java/org/teiid/odbc/ODBCServerRemoteImpl.java @@ -26,7 +26,7 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; -import java.sql.ParameterMetaData; +import java.nio.charset.Charset; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; @@ -50,9 +50,9 @@ import org.teiid.client.security.LogonException; import org.teiid.client.util.ResultsFuture; import org.teiid.core.TeiidProcessingException; -import org.teiid.core.util.ApplicationInfo; import org.teiid.core.util.PropertiesUtils; import org.teiid.core.util.StringUtil; +import org.teiid.core.util.TimestampWithTimezone; import org.teiid.deployers.PgCatalogMetadataStore; import org.teiid.dqp.service.SessionService; import org.teiid.jdbc.ConnectionImpl; @@ -75,6 +75,7 @@ import org.teiid.transport.ODBCClientInstance; import org.teiid.transport.PgBackendProtocol; import org.teiid.transport.PgFrontendProtocol.NullTerminatedStringDataInputStream; +import org.teiid.transport.pg.TimestampUtils; /** * While executing the multiple prepared statements I see this bug currently @@ -82,8 +83,8 @@ */ public class ODBCServerRemoteImpl implements ODBCServerRemote { - private static final boolean HONOR_DECLARE_FETCH_TXN = PropertiesUtils.getBooleanProperty(System.getProperties(), "org.teiid.honorDeclareFetchTxn", false); //$NON-NLS-1$ - + private static final boolean HONOR_DECLARE_FETCH_TXN = PropertiesUtils.getBooleanProperty(System.getProperties(), "org.teiid.honorDeclareFetchTxn", false); //$NON-NLS-1$ + public static final String CONNECTION_PROPERTY_PREFIX = "connection."; //$NON-NLS-1$ private static final String UNNAMED = ""; //$NON-NLS-1$ private static Pattern setPattern = Pattern.compile("set\\s+(\\w+)\\s+to\\s+((?:'[^']*')+)", Pattern.DOTALL|Pattern.CASE_INSENSITIVE);//$NON-NLS-1$ @@ -162,7 +163,7 @@ public class ODBCServerRemoteImpl implements ODBCServerRemote { "\\s+and cn.contype = 'p'\\)" + //$NON-NLS-1$ "\\s+order by ref.oid, ref.i", Pattern.DOTALL|Pattern.CASE_INSENSITIVE); //$NON-NLS-1$ - private static Pattern cursorSelectPattern = Pattern.compile("DECLARE\\s+(\\S+)(?:\\s+INSENSITIVE)?(\\s+(NO\\s+)?SCROLL)?\\s+CURSOR\\s+(?:WITH(?:OUT)? HOLD\\s+)?FOR\\s+(.*)", Pattern.CASE_INSENSITIVE|Pattern.DOTALL); //$NON-NLS-1$ + private static Pattern cursorSelectPattern = Pattern.compile("DECLARE\\s+(\\S+)(\\s+BINARY)?(?:\\s+INSENSITIVE)?(\\s+(NO\\s+)?SCROLL)?\\s+CURSOR\\s+(?:WITH(?:OUT)? HOLD\\s+)?FOR\\s+(.*)", Pattern.CASE_INSENSITIVE|Pattern.DOTALL); //$NON-NLS-1$ private static Pattern fetchPattern = Pattern.compile("FETCH(?:(?:\\s+(FORWARD|ABSOLUTE|RELATIVE))?\\s+(\\d+)\\s+(?:IN|FROM))?\\s+(\\S+)\\s*", Pattern.DOTALL|Pattern.CASE_INSENSITIVE); //$NON-NLS-1$ private static Pattern fetchFirstLastPattern = Pattern.compile("FETCH\\s+(FIRST|LAST)\\s+(?:IN|FROM)\\s+(\\S+)\\s*", Pattern.DOTALL|Pattern.CASE_INSENSITIVE); //$NON-NLS-1$ private static Pattern movePattern = Pattern.compile("MOVE(?:\\s+(FORWARD|BACKWARD))?\\s+(\\d+)\\s+(?:IN|FROM)\\s+(\\S+)\\s*", Pattern.DOTALL|Pattern.CASE_INSENSITIVE); //$NON-NLS-1$ @@ -173,7 +174,7 @@ public class ODBCServerRemoteImpl implements ODBCServerRemote { private static Pattern savepointPattern = Pattern.compile("SAVEPOINT (\\w+\\d?_*)", Pattern.DOTALL|Pattern.CASE_INSENSITIVE); //$NON-NLS-1$ private static Pattern rollbackPattern = Pattern.compile("ROLLBACK\\s*(to)*\\s*(\\w+\\d+_*)*", Pattern.DOTALL|Pattern.CASE_INSENSITIVE); //$NON-NLS-1$ - private static Pattern txnPattern = Pattern.compile("(BEGIN|COMMIT|ROLLBACK)(\\s+(WORK|TRANSACTION))?", Pattern.DOTALL|Pattern.CASE_INSENSITIVE); //$NON-NLS-1$ + private static Pattern txnPattern = Pattern.compile("(BEGIN(?:\\s+READ\\s+ONLY)?|COMMIT|ROLLBACK)(\\s+(WORK|TRANSACTION))?", Pattern.DOTALL|Pattern.CASE_INSENSITIVE); //$NON-NLS-1$ private TeiidDriver driver; private ODBCClientRemote client; @@ -327,7 +328,7 @@ public static void setConnectionProperties(ConnectionImpl conn, } } - private void cursorExecute(String cursorName, final String sql, final ResultsFuture completion, boolean scroll) { + private void cursorExecute(String cursorName, final String sql, final ResultsFuture completion, boolean scroll, final boolean binary) { try { // close if the name is already used or the unnamed prepare; otherwise // stmt is alive until session ends. @@ -355,7 +356,7 @@ public void onCompletion(ResultsFuture future) { try { if (future.get()) { List cols = getPgColInfo(stmt.getResultSet().getMetaData()); - cursorMap.put(name, new Cursor(name, sql, stmt, stmt.getResultSet(), cols)); + cursorMap.put(name, new Cursor(name, sql, stmt, stmt.getResultSet(), cols, binary)); client.sendCommandComplete("DECLARE CURSOR", null); //$NON-NLS-1$ completion.getResultsReceiver().receiveResults(0); } @@ -377,7 +378,7 @@ private void cursorFetch(String cursorName, CursorDirection direction, int rows, if (rows < 1) { throw new SQLException(RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40112, cursorName, rows)); } - this.client.sendResults("FETCH", cursor.rs, cursor.prepared.columnMetadata, completion, direction, rows, true); //$NON-NLS-1$ + this.client.sendResults("FETCH", cursor.rs, cursor.prepared.columnMetadata, completion, direction, rows, true, cursor.resultColumnFormat); //$NON-NLS-1$ } private void cursorMove(String prepareName, String direction, final int rows, final ResultsFuture completion) throws SQLException { @@ -470,7 +471,8 @@ public void onCompletion(ResultsFuture future) { try { if (future.get()) { List cols = getPgColInfo(stmt.getResultSet().getMetaData()); - client.sendResults(sql, stmt.getResultSet(), cols, completion, CursorDirection.FORWARD, -1, true); + String tag = PgBackendProtocol.getCompletionTag(sql, null); + client.sendResults(sql, stmt.getResultSet(), cols, completion, CursorDirection.FORWARD, -1, tag.equals("SELECT") || tag.equals("SHOW"), null); //$NON-NLS-1$ //$NON-NLS-2$ } else { client.sendUpdateCount(sql, stmt.getUpdateCount()); setEncoding(); @@ -508,15 +510,6 @@ public void prepare(String prepareName, String sql, int[] paramType) { //just pull the initial information - leave statement formation until binding String modfiedSQL = fixSQL(sql); stmt = this.connection.prepareStatement(modfiedSQL); - if (paramType.length > 0) { - ParameterMetaData pmd = stmt.getParameterMetaData(); - for (int i = 0; i < paramType.length; i++) { - if (paramType[i] == 0) { - //TODO: this is incorrect - should be oid - //paramType[i] = convertType(pmd.getParameterType(i + 1)); - } - } - } Prepared prepared = new Prepared(prepareName, sql, modfiedSQL, paramType, getPgColInfo(stmt.getMetaData())); this.preparedMap.put(prepareName, prepared); this.client.prepareCompleted(prepareName); @@ -545,7 +538,7 @@ private long readLong(byte[] bytes, int length) { } @Override - public void bindParameters(String bindName, String prepareName, Object[] params, int resultCodeCount, int[] resultColumnFormat) { + public void bindParameters(String bindName, String prepareName, Object[] params, int resultCodeCount, short[] resultColumnFormat, Charset encoding) { // An unnamed portal is destroyed at the end of the transaction, or as soon as // the next Bind statement specifying the unnamed portal as destination is issued. if (bindName == null || bindName.length() == 0) { @@ -576,12 +569,6 @@ public void bindParameters(String bindName, String prepareName, Object[] params, if (param instanceof byte[] && prepared.paramType.length > i) { int oid = prepared.paramType[i]; switch (oid) { - //binary array - //pgobject - //hstore - //date - //time - //timestamp case PGUtil.PG_TYPE_UNSPECIFIED: //TODO: should infer type from the parameter metadata from the parse message break; @@ -602,6 +589,13 @@ public void bindParameters(String bindName, String prepareName, Object[] params, case PGUtil.PG_TYPE_FLOAT8: param = Double.longBitsToDouble(readLong((byte[])param, 8)); break; + case PGUtil.PG_TYPE_DATE: + param = TimestampUtils.toDate(TimestampWithTimezone.getCalendar().getTimeZone(), (int)readLong((byte[])param, 4)); + break; + default: + //start with the string conversion + param = new String((byte[])param, encoding); + break; } } stmt.setObject(i+1, param); @@ -699,7 +693,7 @@ public void onCompletion(ResultsFuture future) { private void sendCursorResults(final Portal cursor, final int fetchSize) { ResultsFuture result = new ResultsFuture (); - this.client.sendResults(null, cursor.rs, cursor.prepared.columnMetadata, result, CursorDirection.FORWARD, fetchSize, false); + this.client.sendResults(null, cursor.rs, cursor.prepared.columnMetadata, result, CursorDirection.FORWARD, fetchSize, false, cursor.resultColumnFormat); result.addCompletionListener(new ResultsFuture.CompletionListener () { public void onCompletion(ResultsFuture future) { try { @@ -768,9 +762,6 @@ else if ((m = fkPattern.matcher(modified)).matches()){ //imported keys return baseQuery + "FKTABLE_NAME = " + m.group(15)+" and FKTABLE_SCHEM = "+m.group(16);//$NON-NLS-1$ //$NON-NLS-2$ } - else if (modified.equalsIgnoreCase("select version()")) { //$NON-NLS-1$ - return "SELECT 'Teiid "+ApplicationInfo.getInstance().getReleaseNumber()+"'"; //$NON-NLS-1$ //$NON-NLS-2$ - } else if (modified.startsWith("SELECT name FROM master..sysdatabases")) { //$NON-NLS-1$ return "SELECT 'Teiid'"; //$NON-NLS-1$ } @@ -793,7 +784,7 @@ else if ((m = setPattern.matcher(sql)).matches()) { return "SET " + m.group(1) + " " + m.group(2); //$NON-NLS-1$ //$NON-NLS-2$ } else if ((m = txnPattern.matcher(sql)).matches()) { - if (m.group(1).equalsIgnoreCase("BEGIN")) { //$NON-NLS-1$ + if (StringUtil.startsWithIgnoreCase(m.group(1), "BEGIN")) { //$NON-NLS-1$ return "START TRANSACTION"; //$NON-NLS-1$ } return m.group(1); @@ -879,7 +870,7 @@ public void getParameterDescription(String prepareName) { // followed by a RowDescription message describing the rows that will be returned when the statement // is eventually executed (or a NoData message if the statement will not return rows). - this.client.sendResultSetDescription(query.columnMetadata); + this.client.sendResultSetDescription(query.columnMetadata, null); } private void errorOccurred(String error) { @@ -908,7 +899,7 @@ public void getResultSetMetaDataDescription(String bindName) { errorOccurred(RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40078, bindName)); } else { - this.client.sendResultSetDescription(query.prepared.columnMetadata); + this.client.sendResultSetDescription(query.prepared.columnMetadata, query.resultColumnFormat); } } @@ -1119,10 +1110,10 @@ public void onCompletion(ResultsFuture future) { Matcher m = null; if ((m = cursorSelectPattern.matcher(sql)).matches()){ boolean scroll = false; - if (m.group(2) != null && m.group(3) == null ) { + if (m.group(3) != null && m.group(4) == null ) { scroll = true; } - cursorExecute(normalizeName(m.group(1)), fixSQL(m.group(4)), results, scroll); + cursorExecute(normalizeName(m.group(1)), fixSQL(m.group(5)), results, scroll, m.group(2) != null); } else if ((m = fetchPattern.matcher(sql)).matches()){ int rowCount = 1; @@ -1192,7 +1183,8 @@ private List getPgColInfo(ResultSetMetaData meta) final PgColInfo info = new PgColInfo(); info.name = meta.getColumnLabel(i); info.type = meta.getColumnType(i); - info.type = convertType(info.type); + String typeName = meta.getColumnTypeName(i); + info.type = convertType(info.type, typeName); info.precision = meta.getColumnDisplaySize(i); if (info.type == PG_TYPE_NUMERIC) { info.mod = 4+ 65536*Math.min(32767, meta.getPrecision(i))+Math.min(32767, meta.getScale(i)); @@ -1269,7 +1261,7 @@ public Prepared (String name, String sql, String modifiedSql, int[] paramType, L */ static class Portal { - public Portal(String name, Prepared prepared, int[] resultColumnformat, PreparedStatementImpl stmt) { + public Portal(String name, Prepared prepared,short[] resultColumnformat, PreparedStatementImpl stmt) { this.name = name; this.prepared = prepared; this.resultColumnFormat = resultColumnformat; @@ -1283,7 +1275,7 @@ public Portal(String name, Prepared prepared, int[] resultColumnformat, Prepared /** * The format used in the result set columns (if set). */ - final int[] resultColumnFormat; + final short[] resultColumnFormat; final Prepared prepared; @@ -1297,8 +1289,8 @@ public Portal(String name, Prepared prepared, int[] resultColumnformat, Prepared static class Cursor extends Portal { - public Cursor (String name, String sql, PreparedStatementImpl stmt, ResultSetImpl rs, List colMetadata) { - super(name, new Prepared(UNNAMED, sql, sql, null, colMetadata), null, stmt); + public Cursor (String name, String sql, PreparedStatementImpl stmt, ResultSetImpl rs, List colMetadata, boolean binary) { + super(name, new Prepared(UNNAMED, sql, sql, null, colMetadata), binary?new short[] {1}:null, stmt); this.rs = rs; } } diff --git a/runtime/src/main/java/org/teiid/odbc/PGUtil.java b/runtime/src/main/java/org/teiid/odbc/PGUtil.java index 89530ffdf0..db47d2095d 100644 --- a/runtime/src/main/java/org/teiid/odbc/PGUtil.java +++ b/runtime/src/main/java/org/teiid/odbc/PGUtil.java @@ -36,10 +36,14 @@ public class PGUtil { public static final int PG_TYPE_INT2 = 21; public static final int PG_TYPE_INT4 = 23; public static final int PG_TYPE_TEXT = 25; + public static final int PG_TYPE_XML = 142; //private static final int PG_TYPE_OID = 26; public static final int PG_TYPE_FLOAT4 = 700; public static final int PG_TYPE_FLOAT8 = 701; public static final int PG_TYPE_UNKNOWN = 705; + + public static final int PG_TYPE_GEOMETRY = 32816; + public static final int PG_TYPE_GEOMETRYARRAY = 32824; public static final int PG_TYPE_OIDVECTOR = 30; public static final int PG_TYPE_INT2VECTOR = 22; @@ -51,6 +55,19 @@ public class PGUtil { public static final int PG_TYPE_TIME = 1083; public static final int PG_TYPE_TIMESTAMP_NO_TMZONE = 1114; public static final int PG_TYPE_NUMERIC = 1700; + + public static final int PG_TYPE_BOOLARRAY = 1000; + public static final int PG_TYPE_BYTEAARRAY = 1001; + public static final int PG_TYPE_INT8ARRAY = 1026; + public static final int PG_TYPE_INT2ARRAY = 1005; + public static final int PG_TYPE_INT4ARRAY = 1007; + public static final int PG_TYPE_FLOAT4ARRAY = 1021; + public static final int PG_TYPE_FLOAT8ARRAY = 1022; + public static final int PG_TYPE_DATEARRAY = 1182; + public static final int PG_TYPE_TIMEARRAY = 1183; + public static final int PG_TYPE_TIMESTAMP_NO_TMZONEARRAY = 1115; + public static final int PG_TYPE_NUMERICARRAY = 1031; + public static final int PG_TYPE_XMLARRAY = 143; //private static final int PG_TYPE_LO = 14939; public static class PgColInfo { @@ -61,11 +78,11 @@ public static class PgColInfo { public int precision; public int mod = -1; } - + /** * Types.ARRAY is not supported */ - public static int convertType(final int type) { + public static int convertType(final int type, final String typeName) { switch (type) { case Types.BIT: case Types.BOOLEAN: @@ -100,15 +117,46 @@ public static int convertType(final int type) { case Types.BINARY: case Types.VARBINARY: case Types.LONGVARBINARY: + if (typeName.equals("geometry")) { //$NON-NLS-1$ + return PG_TYPE_GEOMETRY; + } return PG_TYPE_BYTEA; + + case Types.SQLXML: + return PG_TYPE_XML; case Types.LONGVARCHAR: case Types.CLOB: - case Types.SQLXML: return PG_TYPE_TEXT; case Types.ARRAY: - return PG_TYPE_TEXTARRAY; + if (typeName.equals("boolean[]")) //$NON-NLS-1$ + return PG_TYPE_BOOLARRAY; + if (typeName.equals("byte[]") || //$NON-NLS-1$ + typeName.equals("short[]")) //$NON-NLS-1$ + return PG_TYPE_INT2ARRAY; + if (typeName.equals("integer[]")) //$NON-NLS-1$ + return PG_TYPE_INT4ARRAY; + if (typeName.equals("long[]")) //$NON-NLS-1$ + return PG_TYPE_INT8ARRAY; + if (typeName.equals("float[]")) //$NON-NLS-1$ + return PG_TYPE_FLOAT4ARRAY; + if (typeName.equals("double[]")) //$NON-NLS-1$ + return PG_TYPE_FLOAT8ARRAY; + if (typeName.equals("biginiteger[]") || //$NON-NLS-1$ + typeName.equals("bigdecimal[]")) //$NON-NLS-1$ + return PG_TYPE_NUMERICARRAY; + if (typeName.equals("date[]")) //$NON-NLS-1$ + return PG_TYPE_DATEARRAY; + if (typeName.equals("time[]")) //$NON-NLS-1$ + return PG_TYPE_TIMEARRAY; + if (typeName.equals("timestamp[]")) //$NON-NLS-1$ + return PG_TYPE_TIMESTAMP_NO_TMZONEARRAY; + if (typeName.equals("geometry[]")) //$NON-NLS-1$ + return PG_TYPE_GEOMETRYARRAY; + if (typeName.equals("xml[]")) //$NON-NLS-1$ + return PG_TYPE_XMLARRAY; + return PG_TYPE_TEXTARRAY; default: return PG_TYPE_UNKNOWN; diff --git a/runtime/src/main/java/org/teiid/odbc/ScriptReader.java b/runtime/src/main/java/org/teiid/odbc/ScriptReader.java index 49ee9b5ee5..d1ac02c45a 100644 --- a/runtime/src/main/java/org/teiid/odbc/ScriptReader.java +++ b/runtime/src/main/java/org/teiid/odbc/ScriptReader.java @@ -198,16 +198,7 @@ private String readStatementLoop() throws IOException { if (expressionStart != -1 && expressionEnd == start -1) { //special handling for regclass cast - it won't always work if ("regclass".equalsIgnoreCase(type)) { //$NON-NLS-1$ - String name = builder.substring(expressionStart); - if (name.startsWith("'\"") && name.length() > 4) { //$NON-NLS-1$ - name = name.substring(2, name.length()-2); - name = '\''+ name + '\''; - } - if (name.startsWith("'")) { //$NON-NLS-1$ - builder.setLength(expressionStart); - builder.append(name.toUpperCase()); - } - builder.insert(expressionStart, "(SELECT oid FROM pg_class WHERE upper(relname) = "); //$NON-NLS-1$ + builder.insert(expressionStart, "regclass("); //$NON-NLS-1$ builder.append(")"); //$NON-NLS-1$ } else if ("regproc".equalsIgnoreCase(type)) { //$NON-NLS-1$ String name = builder.substring(expressionStart); diff --git a/runtime/src/main/java/org/teiid/transport/PgBackendProtocol.java b/runtime/src/main/java/org/teiid/transport/PgBackendProtocol.java index ca0075a88e..fe5562d92d 100644 --- a/runtime/src/main/java/org/teiid/transport/PgBackendProtocol.java +++ b/runtime/src/main/java/org/teiid/transport/PgBackendProtocol.java @@ -29,10 +29,12 @@ import java.io.Writer; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.nio.ByteOrder; import java.nio.charset.Charset; import java.security.GeneralSecurityException; import java.sql.Array; import java.sql.Blob; +import java.sql.Date; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; @@ -53,10 +55,13 @@ import org.jboss.netty.handler.ssl.SslHandler; import org.teiid.client.util.ResultsFuture; import org.teiid.core.types.ArrayImpl; +import org.teiid.core.types.GeometryType; import org.teiid.core.util.ObjectConverterUtil; +import org.teiid.core.util.PropertiesUtils; import org.teiid.core.util.ReflectionHelper; import org.teiid.core.util.SqlUtil; import org.teiid.core.util.StringUtil; +import org.teiid.core.util.TimestampWithTimezone; import org.teiid.jdbc.ResultSetImpl; import org.teiid.jdbc.TeiidSQLException; import org.teiid.logging.LogConstants; @@ -65,8 +70,10 @@ import org.teiid.net.socket.ServiceInvocationStruct; import org.teiid.odbc.ODBCClientRemote; import org.teiid.odbc.PGUtil.PgColInfo; +import org.teiid.query.function.GeometryUtils; import org.teiid.runtime.RuntimePlugin; import org.teiid.transport.pg.PGbytea; +import org.teiid.transport.pg.TimestampUtils; /** * Represents the messages going from Server --> PG ODBC Client * Some parts of this code is taken from H2's implementation of ODBC @@ -98,16 +105,18 @@ private final class ResultsWorkItem implements Runnable { private final List cols; private final ResultSetImpl rs; private final ResultsFuture result; + private final short[] resultColumnFormat; private int rows2Send; private int rowsSent = 0; private int rowsInBuffer = 0; String sql; - private ResultsWorkItem(List cols, ResultSetImpl rs, ResultsFuture result, int rows2Send) { + private ResultsWorkItem(List cols, ResultSetImpl rs, ResultsFuture result, int rows2Send, short[] resultColumnFormat) { this.cols = cols; this.rs = rs; this.result = result; this.rows2Send = rows2Send; + this.resultColumnFormat = resultColumnFormat; initBuffer(maxBufferSize / 8); } @@ -145,7 +154,7 @@ private boolean processRow(ResultsFuture future) { boolean processNext = true; try { if (future.get()) { - sendDataRow(rs, cols); + sendDataRow(rs, cols, resultColumnFormat); rowsSent++; rowsInBuffer++; boolean done = rowsSent == rows2Send; @@ -349,19 +358,21 @@ public void sendParameterDescription(int[] paramType) { } @Override - public void sendResultSetDescription(List cols) { - sendRowDescription(cols); + public void sendResultSetDescription(List cols, + short[] resultColumnFormat) { + sendRowDescription(cols, resultColumnFormat); } @Override public void sendResults(String sql, ResultSetImpl rs, List cols, - ResultsFuture result, CursorDirection direction, int rowCount, boolean describeRows) { + ResultsFuture result, CursorDirection direction, + int rowCount, boolean describeRows, short[] resultColumnFormat) { if (nextFuture != null) { sendErrorResponse(new IllegalStateException("Pending results have not been sent")); //$NON-NLS-1$ } if (describeRows) { - sendRowDescription(cols); + sendRowDescription(cols, resultColumnFormat); } ResultsWorkItem r; try { @@ -384,7 +395,7 @@ public void sendResults(String sql, ResultSetImpl rs, List cols, } rowCount = 1; } - r = new ResultsWorkItem(cols, rs, result, rowCount); + r = new ResultsWorkItem(cols, rs, result, rowCount, resultColumnFormat); r.sql = sql; if (singleResult != null) { ResultsFuture resultsFuture = new ResultsFuture (); @@ -441,10 +452,16 @@ private void sendEmptyQueryResponse() { @Override public void sendCommandComplete(String sql, Integer count) { startMessage('C'); + String tag = getCompletionTag(sql, count); + writeString(tag); + sendMessage(); + } + + public static String getCompletionTag(String sql, Integer count) { String tag; if (StringUtil.startsWithIgnoreCase(sql, "BEGIN") || StringUtil.startsWithIgnoreCase(sql, "START TRANSACTION")) { tag = "BEGIN"; - } else if (sql.indexOf(' ') == -1) { + } else if (sql.indexOf(' ') == -1 || sql.equals("CLOSE CURSOR")) { //should already be a completion tag tag = sql.toUpperCase(); if (count != null) { @@ -457,22 +474,26 @@ public void sendCommandComplete(String sql, Integer count) { if (tag.equals("EXEC") || tag.equals("CALL")) { tag = "SELECT"; } - if (count != null) { + if (count != null && !(tag.equalsIgnoreCase("ROLLBACK") || tag.equalsIgnoreCase("SAVEPOINT") || tag.equalsIgnoreCase("RELEASE"))) { tag += " " + count; } } - writeString(tag); - sendMessage(); + return tag; } - private void sendDataRow(ResultSet rs, List cols) throws SQLException, IOException { + private void sendDataRow(ResultSet rs, List cols, short[] resultColumnFormat) throws SQLException, IOException { startMessage('D', -1); int lengthIndex = this.dataOut.writerIndex() - 4; writeShort(cols.size()); for (int i = 0; i < cols.size(); i++) { int dataBytesIndex = this.dataOut.writerIndex(); writeInt(-1); - getContent(rs, cols.get(i), i+1); + if (!isBinary(cols.get(i).type) + || (resultColumnFormat==null || (resultColumnFormat.length==1?resultColumnFormat[0]==0:resultColumnFormat[i]==0))) { + getContent(rs, cols.get(i), i+1); + } else { + getBinaryContent(rs, cols.get(i), i+1); + } writer.flush(); if (!rs.wasNull()) { int bytes = this.dataOut.writerIndex() - dataBytesIndex - 4; @@ -482,6 +503,63 @@ private void sendDataRow(ResultSet rs, List cols) throws SQLException this.dataOut.setInt(lengthIndex, this.dataOut.writerIndex() - lengthIndex); } + private void getBinaryContent(ResultSet rs, PgColInfo col, int column) throws SQLException, TeiidSQLException, IOException { + switch (col.type) { + case PG_TYPE_INT2: + short sval = rs.getShort(column); + if (!rs.wasNull()) { + dataOut.writeShort(sval); + } + break; + case PG_TYPE_INT4: + int ival = rs.getInt(column); + if (!rs.wasNull()) { + dataOut.writeInt(ival); + } + break; + case PG_TYPE_INT8: + long lval = rs.getLong(column); + if (!rs.wasNull()) { + dataOut.writeLong(lval); + } + break; + case PG_TYPE_FLOAT4: + float fval = rs.getFloat(column); + if (!rs.wasNull()) { + dataOut.writeInt(Float.floatToIntBits(fval)); + } + break; + case PG_TYPE_FLOAT8: + double dval = rs.getDouble(column); + if (!rs.wasNull()) { + dataOut.writeLong(Double.doubleToLongBits(dval)); + } + break; + case PG_TYPE_BYTEA: + Blob blob = rs.getBlob(column); + if (blob != null) { + try { + byte[] bytes = ObjectConverterUtil.convertToByteArray(blob.getBinaryStream(), this.maxLobSize); + write(bytes); + } catch(OutOfMemoryError e) { + throw new StreamCorruptedException("data too big: " + e.getMessage()); //$NON-NLS-1$ + } + } + break; + case PG_TYPE_DATE: + Date d = rs.getDate(column); + if (d != null) { + long millis = d.getTime(); + millis += TimestampWithTimezone.getCalendar().getTimeZone().getOffset(millis); + long secs = TimestampUtils.toPgSecs(millis / 1000); + dataOut.writeInt((int) (secs / 86400)); + } + break; + default: + throw new AssertionError(); + } + } + private void getContent(ResultSet rs, PgColInfo col, int column) throws SQLException, TeiidSQLException, IOException { switch (col.type) { case PG_TYPE_BOOL: @@ -501,7 +579,15 @@ private void getContent(ResultSet rs, PgColInfo col, int column) throws SQLExcep writer.write(value); } break; - + case PG_TYPE_GEOMETRY: + Object val = rs.getObject(column); + if (val != null) { + Blob blob = GeometryUtils.geometryToEwkb((GeometryType)rs.unwrap(ResultSetImpl.class).getRawCurrentValue()); + String hexewkb = PropertiesUtils.toHex(blob.getBytes(1, (int) blob.length())); + writer.write(hexewkb); + } + break; + case PG_TYPE_XML: case PG_TYPE_TEXT: Reader r = rs.getCharacterStream(column); if (r != null) { @@ -528,6 +614,16 @@ private void getContent(ResultSet rs, PgColInfo col, int column) throws SQLExcep case PG_TYPE_CHARARRAY: case PG_TYPE_TEXTARRAY: case PG_TYPE_OIDARRAY: + case PG_TYPE_BOOLARRAY: + case PG_TYPE_INT2ARRAY: + case PG_TYPE_INT4ARRAY: + case PG_TYPE_INT8ARRAY: + case PG_TYPE_FLOAT4ARRAY: + case PG_TYPE_FLOAT8ARRAY: + case PG_TYPE_NUMERICARRAY: + case PG_TYPE_DATEARRAY: + case PG_TYPE_TIMEARRAY: + case PG_TYPE_TIMESTAMP_NO_TMZONEARRAY: { Array obj = rs.getArray(column); if (obj != null) { @@ -580,7 +676,11 @@ private void getContent(ResultSet rs, PgColInfo col, int column) throws SQLExcep break; default: - throw new TeiidSQLException("unknown datatype failed to convert"); + Object obj = rs.getObject(column); + if (obj != null) { + throw new TeiidSQLException("unknown datatype "+ col.type+ " failed to convert"); + } + break; } } @@ -647,7 +747,21 @@ private void sendErrorResponse(Throwable t) { sendMessage(); } - private void sendRowDescription(List cols) { + boolean isBinary(int oid) { + switch (oid) { + case PG_TYPE_INT2: + case PG_TYPE_INT4: + case PG_TYPE_INT8: + case PG_TYPE_FLOAT4: + case PG_TYPE_FLOAT8: + case PG_TYPE_BYTEA: + case PG_TYPE_DATE: + return true; + } + return false; + } + + private void sendRowDescription(List cols, short[] resultColumnFormat) { if (cols == null) { //send NoData startMessage('n'); @@ -656,7 +770,8 @@ private void sendRowDescription(List cols) { } startMessage('T'); writeShort(cols.size()); - for (PgColInfo info : cols) { + for (int i = 0; i < cols.size(); i++) { + PgColInfo info = cols.get(i); writeString(info.name); // rel ID writeInt(info.reloid); @@ -668,8 +783,12 @@ private void sendRowDescription(List cols) { writeShort(getTypeSize(info.type, info.precision)); // pg_attribute.atttypmod writeInt(info.mod); - // text - writeShort(0); + if (!isBinary(info.type) || resultColumnFormat == null) { + //text + writeShort(0); + } else { + writeShort(resultColumnFormat[resultColumnFormat.length == 1?0:i]); + } } sendMessage(); } @@ -829,7 +948,7 @@ private void startMessage(char newMessageType, int estimatedLength) { } private void initBuffer(int estimatedLength) { - this.dataOut = ChannelBuffers.dynamicBuffer(estimatedLength); + this.dataOut = ChannelBuffers.dynamicBuffer(ByteOrder.BIG_ENDIAN, estimatedLength); ChannelBufferOutputStream cbos = new ChannelBufferOutputStream(this.dataOut); this.writer = new OutputStreamWriter(cbos, this.encoding); } diff --git a/runtime/src/main/java/org/teiid/transport/PgFrontendProtocol.java b/runtime/src/main/java/org/teiid/transport/PgFrontendProtocol.java index 86a339b353..c5afded03c 100644 --- a/runtime/src/main/java/org/teiid/transport/PgFrontendProtocol.java +++ b/runtime/src/main/java/org/teiid/transport/PgFrontendProtocol.java @@ -218,6 +218,7 @@ private Object buildInitialize(NullTerminatedStringDataInputStream data, Channel String clientEncoding = props.getProperty("client_encoding", PgBackendProtocol.DEFAULT_ENCODING); props.setProperty("client_encoding", clientEncoding); props.setProperty("default_transaction_isolation", "read committed"); + props.setProperty("integer_datetimes", "on"); props.setProperty("DateStyle", "ISO"); props.setProperty("TimeZone", Calendar.getInstance().getTimeZone().getDisplayName()); this.odbcProxy.initialize(props); @@ -273,11 +274,14 @@ private Object buildBind(NullTerminatedStringDataInputStream data) throws IOExce } int resultCodeCount = data.readShort(); - int[] resultColumnFormat = new int[resultCodeCount]; - for (int i = 0; i < resultCodeCount; i++) { - resultColumnFormat[i] = data.readShort(); + short[] resultColumnFormat = null; + if (resultCodeCount != 0) { + resultColumnFormat = new short[resultCodeCount]; + for (int i = 0; i < resultCodeCount; i++) { + resultColumnFormat[i] = data.readShort(); + } } - this.odbcProxy.bindParameters(bindName, prepName, params, resultCodeCount, resultColumnFormat); + this.odbcProxy.bindParameters(bindName, prepName, params, resultCodeCount, resultColumnFormat, this.pgBackendProtocol.getEncoding()); return message; } diff --git a/runtime/src/main/java/org/teiid/transport/pg/TimestampUtils.java b/runtime/src/main/java/org/teiid/transport/pg/TimestampUtils.java new file mode 100644 index 0000000000..e246601f9c --- /dev/null +++ b/runtime/src/main/java/org/teiid/transport/pg/TimestampUtils.java @@ -0,0 +1,97 @@ +/*------------------------------------------------------------------------- +* +* Copyright (c) 2003-2011, PostgreSQL Global Development Group +* +* +*------------------------------------------------------------------------- +*/ +package org.teiid.transport.pg; + +import java.sql.Date; +import java.util.TimeZone; + +/** + * Slimmed down from org.postgresql.jdbc2.TimestampUtils + */ +public class TimestampUtils { + + public static final long DATE_POSITIVE_INFINITY = 9223372036825200000l; + public static final long DATE_NEGATIVE_INFINITY = -9223372036832400000l; + public static final long DATE_POSITIVE_SMALLER_INFINITY = 185543533774800000l; + public static final long DATE_NEGATIVE_SMALLER_INFINITY = -185543533774800000l; + + /** + * Returns the SQL Date object matching the given bytes with + * {@link Oid#DATE}. + * + * @param tz The timezone used. + * @param bytes The binary encoded date value. + * @return The parsed date object. + */ + public static Date toDate(TimeZone tz, int days) { + long secs = toJavaSecs(days * 86400L); + long millis = secs * 1000L; + int offset = tz.getOffset(millis); + if (millis <= DATE_NEGATIVE_SMALLER_INFINITY) { + millis = DATE_NEGATIVE_INFINITY; + offset = 0; + } else if (millis >= DATE_POSITIVE_SMALLER_INFINITY) { + millis = DATE_POSITIVE_INFINITY; + offset = 0; + } + return new Date(millis - offset); + } + + /** + * Converts the given postgresql seconds to java seconds. + * Reverse engineered by inserting varying dates to postgresql + * and tuning the formula until the java dates matched. + * See {@link #toPgSecs} for the reverse operation. + * + * @param secs Postgresql seconds. + * @return Java seconds. + */ + private static long toJavaSecs(long secs) { + // postgres epoc to java epoc + secs += 946684800L; + + // Julian/Gregorian calendar cutoff point + if (secs < -12219292800L) { // October 4, 1582 -> October 15, 1582 + secs += 86400 * 10; + if (secs < -14825808000L) { // 1500-02-28 -> 1500-03-01 + int extraLeaps = (int) ((secs + 14825808000L) / 3155760000L); + extraLeaps--; + extraLeaps -= extraLeaps / 4; + secs += extraLeaps * 86400L; + } + } + return secs; + } + + /** + * Converts the given java seconds to postgresql seconds. + * See {@link #toJavaSecs} for the reverse operation. + * The conversion is valid for any year 100 BC onwards. + * + * @param secs Postgresql seconds. + * @return Java seconds. + */ + public static long toPgSecs(long secs) { + // java epoc to postgres epoc + secs -= 946684800L; + + // Julian/Greagorian calendar cutoff point + if (secs < -13165977600L) { // October 15, 1582 -> October 4, 1582 + secs -= 86400 * 10; + if (secs < -15773356800L) { // 1500-03-01 -> 1500-02-28 + int years = (int) ((secs + 15773356800L) / -3155823050L); + years++; + years -= years/4; + secs += years * 86400; + } + } + + return secs; + } + +} diff --git a/runtime/src/test/java/org/teiid/odbc/TestScriptReader.java b/runtime/src/test/java/org/teiid/odbc/TestScriptReader.java index 7076837c3d..673c63a5c1 100644 --- a/runtime/src/test/java/org/teiid/odbc/TestScriptReader.java +++ b/runtime/src/test/java/org/teiid/odbc/TestScriptReader.java @@ -75,9 +75,9 @@ public class TestScriptReader { @Test public void testRegClassCast() throws Exception { ScriptReader sr = new ScriptReader("where oid='\"a\"'::regclass"); - sr.setRewrite(true);; + sr.setRewrite(true); String result = sr.readStatement(); - assertEquals("where oid=(SELECT oid FROM pg_class WHERE upper(relname) = 'A')", result); + assertEquals("where oid=regclass('\"a\"')", result); } @Test public void testExtraDelim() throws Exception { diff --git a/test-integration/common/src/test/java/org/teiid/jdbc/TestDynamicImportedMetaData.java b/test-integration/common/src/test/java/org/teiid/jdbc/TestDynamicImportedMetaData.java index 1fb5526247..16932c3aeb 100644 --- a/test-integration/common/src/test/java/org/teiid/jdbc/TestDynamicImportedMetaData.java +++ b/test-integration/common/src/test/java/org/teiid/jdbc/TestDynamicImportedMetaData.java @@ -128,10 +128,10 @@ private MetadataFactory createMetadataFactory(String schema, Properties importPr Properties importProperties = new Properties(); importProperties.setProperty("importer.importProcedures", Boolean.TRUE.toString()); - importProperties.setProperty("importer.excludeTables", "VDB\\.SYS.*"); + importProperties.setProperty("importer.excludeTables", "VDB\\.(SYS|pg_catalog).*"); importProperties.setProperty("importer.excludeProcedures", "VDB\\..*"); MetadataFactory mf = getMetadata(importProperties, conn); - assertEquals(String.valueOf(mf.asMetadataStore().getSchemas().get("TEST").getTables()), 17, mf.asMetadataStore().getSchemas().get("TEST").getTables().size()); + assertEquals(String.valueOf(mf.asMetadataStore().getSchemas().get("TEST").getTables()), 3, mf.asMetadataStore().getSchemas().get("TEST").getTables().size()); assertEquals(0, mf.asMetadataStore().getSchemas().get("TEST").getProcedures().size()); } diff --git a/test-integration/common/src/test/java/org/teiid/jdbc/TestInternalConnection.java b/test-integration/common/src/test/java/org/teiid/jdbc/TestInternalConnection.java index 531778773a..50068c850f 100644 --- a/test-integration/common/src/test/java/org/teiid/jdbc/TestInternalConnection.java +++ b/test-integration/common/src/test/java/org/teiid/jdbc/TestInternalConnection.java @@ -28,6 +28,7 @@ import java.net.InetSocketAddress; import java.sql.Connection; import java.sql.ResultSet; +import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; @@ -157,9 +158,14 @@ public static String doSomething(CommandContext cc, Integer val) throws SQLExcep try { TeiidDriver driver = es.getDriver(); conn = driver.connect("jdbc:teiid:test", null); - ResultSet rs = conn.createStatement().executeQuery("select func(2)"); + //execute multiple to check for an id conflict + ResultSet rs = conn.createStatement().executeQuery("select func(2) union all select func(3)"); rs.next(); assertEquals("anonymous@teiid-securityHELLO WORLD2", rs.getString(1)); + rs.next(); + assertEquals("anonymous@teiid-securityHELLO WORLD3", rs.getString(1)); + ResultSetMetaData metadata = rs.getMetaData(); + assertNotNull(metadata.getColumnName(1)); } finally { if (conn != null) { conn.close(); diff --git a/test-integration/common/src/test/java/org/teiid/jdbc/TestMMDatabaseMetaData.java b/test-integration/common/src/test/java/org/teiid/jdbc/TestMMDatabaseMetaData.java index ed28082c09..e07bf08987 100644 --- a/test-integration/common/src/test/java/org/teiid/jdbc/TestMMDatabaseMetaData.java +++ b/test-integration/common/src/test/java/org/teiid/jdbc/TestMMDatabaseMetaData.java @@ -49,6 +49,7 @@ import org.teiid.core.CoreConstants; import org.teiid.core.util.ApplicationInfo; import org.teiid.core.util.ObjectConverterUtil; +import org.teiid.core.util.PropertiesUtils; import org.teiid.core.util.UnitTestUtil; import org.teiid.jdbc.util.ResultSetUtil; @@ -58,7 +59,7 @@ @SuppressWarnings("nls") public class TestMMDatabaseMetaData { - private static final boolean REPLACE_EXPECTED = false; + private static final boolean REPLACE_EXPECTED = PropertiesUtils.getBooleanProperty(System.getProperties(), "replace_expected", false); private static final boolean WRITE_ACTUAL_RESULTS_TO_FILE = false; private static final boolean PRINT_RESULTSETS_TO_CONSOLE = false; diff --git a/test-integration/common/src/test/java/org/teiid/systemmodel/TestODBCSchema.java b/test-integration/common/src/test/java/org/teiid/systemmodel/TestODBCSchema.java index 7abcb3122a..27d96e4df0 100644 --- a/test-integration/common/src/test/java/org/teiid/systemmodel/TestODBCSchema.java +++ b/test-integration/common/src/test/java/org/teiid/systemmodel/TestODBCSchema.java @@ -45,6 +45,7 @@ public TestODBCSchema() { if (this.internalConnection != null) { this.internalConnection.close(); } + server.undeployVDB("x"); } @Test public void test_PG_AM() throws Exception { @@ -168,5 +169,37 @@ public boolean isSourceRequired() { server.undeployVDB("dup"); } } + + @Test public void testTypes() throws Exception { + ModelMetaData mmd = new ModelMetaData(); + mmd.setName("x"); + mmd.setModelType(Type.VIRTUAL); + mmd.addSourceMetadata("ddl", "create view v as select null, cast(null as xml), cast(null as boolean), cast(null as byte), cast(null as short), cast(null as integer), cast(null as long)," + + " cast(null as float), cast(null as double), cast(null as bigdecimal), cast(null as biginteger), cast(null as time), cast(null as date), cast(null as timestamp), cast(null as varbinary), " + + " cast(null as char), cast(null as string), cast(null as clob), cast(null as blob), " + + " cast(null as xml[]), cast(null as boolean[]), cast(null as byte[]), cast(null as short[]), cast(null as integer[]), cast(null as long[]), cast(null as bigdecimal[]), cast(null as biginteger[]), " + + " cast(null as float[]), cast(null as double[]), cast(null as time[]), cast(null as date[]), cast(null as timestamp[]), cast(null as varbinary[]), " + + " cast(null as char[]), cast(null as string[]), cast(null as clob[]), cast(null as blob[])"); + server.deployVDB("x", mmd); + + this.internalConnection.close(); + this.internalConnection = server.createConnection("jdbc:teiid:x"); //$NON-NLS-1$ //$NON-NLS-2$ + + execute("select count(oid) = count(distinct oid) from pg_attribute"); //$NON-NLS-1$ + this.internalResultSet.next(); + assertTrue(this.internalResultSet.getBoolean(1)); + + execute("select oid from pg_class where relname = 'v'"); //$NON-NLS-1$ + this.internalResultSet.next(); + int val = this.internalResultSet.getInt(1); + + String sql = "select n.nspname, c.relname, a.attname, a.atttypid, t.typname, a.attnum, a.attlen, a.atttypmod, a.attnotnull, c.relhasrules, c.relkind, c.oid, pg_get_expr(d.adbin, d.adrelid), case t.typtype when 'd' then t.typbasetype else 0 end, t.typtypmod, c.relhasoids " + + "from (((pg_catalog.pg_class c inner join pg_catalog.pg_namespace n on n.oid = c.relnamespace and " + + "c.oid = ?) inner join pg_catalog.pg_attribute a on (not a.attisdropped) and a.attnum > 0 and a.attrelid = c.oid) inner join pg_catalog.pg_type t on t.oid = a.atttypid) left outer join pg_attrdef d on a.atthasdef and d.adrelid = a.attrelid and d.adnum = a.attnum order by n.nspname, c.relname, attnum"; + + execute(sql, new Object[] {val}); + + TestMMDatabaseMetaData.compareResultSet(this.internalResultSet); + } } diff --git a/test-integration/common/src/test/java/org/teiid/systemmodel/TestPGMetadata.java b/test-integration/common/src/test/java/org/teiid/systemmodel/TestPGMetadata.java index b054112b74..a08c9d87e6 100644 --- a/test-integration/common/src/test/java/org/teiid/systemmodel/TestPGMetadata.java +++ b/test-integration/common/src/test/java/org/teiid/systemmodel/TestPGMetadata.java @@ -28,12 +28,10 @@ import org.teiid.adminapi.Model.Type; import org.teiid.adminapi.impl.ModelMetaData; import org.teiid.adminapi.impl.VDBMetaData; -import org.teiid.deployers.VirtualDatabaseException; -import org.teiid.dqp.internal.datamgr.ConnectorManagerRepository.ConnectorManagerException; import org.teiid.jdbc.AbstractMMQueryTestCase; import org.teiid.jdbc.FakeServer; -import org.teiid.translator.TranslatorException; +@SuppressWarnings("nls") public class TestPGMetadata extends AbstractMMQueryTestCase { static FakeServer server = null; @@ -47,9 +45,7 @@ public static void teardown() { server.stop(); } - private static VDBMetaData buildVDB(String name) - throws ConnectorManagerException, VirtualDatabaseException, - TranslatorException { + private static VDBMetaData buildVDB(String name) { VDBMetaData vdb = new VDBMetaData(); vdb.setName(name); ModelMetaData mmd = new ModelMetaData(); @@ -78,21 +74,28 @@ public void test_PG_Metadata_ON() throws Exception { vdb.addProperty("include-pg-metadata", "true"); server.deployVDB(vdb); this.internalConnection = server.createConnection("jdbc:teiid:y"); //$NON-NLS-1$ //$NON-NLS-2$ - try { - execute("select * FROM pg_am"); //$NON-NLS-1$ - } catch (Exception e) { - Assert.fail("there should be PG metadata"); - } + execute("select * FROM pg_am"); //$NON-NLS-1$ } @Test public void test_PG_Metadata_DEFAULT() throws Exception { VDBMetaData vdb = buildVDB("z"); server.deployVDB(vdb); this.internalConnection = server.createConnection("jdbc:teiid:z"); //$NON-NLS-1$ //$NON-NLS-2$ - try { - execute("select * FROM pg_am"); //$NON-NLS-1$ - } catch (Exception e) { - Assert.fail("there should be PG metadata"); - } + execute("select * FROM pg_am"); //$NON-NLS-1$ } + + @Test public void testTypes() throws Exception { + VDBMetaData vdb = buildVDB("t"); + server.deployVDB(vdb); + this.internalConnection = server.createConnection("jdbc:teiid:t"); //$NON-NLS-1$ //$NON-NLS-2$ + execute("select format_type((select oid from pg_type where typname = '_int2'), 0)"); //$NON-NLS-1$ + assertResults(new String[] {"expr1[string]", "smallint[]"}); + + execute("select format_type((select oid from pg_type where typname = 'float4'), 0)"); //$NON-NLS-1$ + assertResults(new String[] {"expr1[string]", "real"}); + + execute("select format_type((select oid from pg_type where typname = 'numeric'), 100)"); //$NON-NLS-1$ + assertResults(new String[] {"expr1[string]", "numeric(0,96)"}); + } + } diff --git a/test-integration/common/src/test/java/org/teiid/transport/TestODBCSocketTransport.java b/test-integration/common/src/test/java/org/teiid/transport/TestODBCSocketTransport.java index 92e9293d68..58cbad1596 100644 --- a/test-integration/common/src/test/java/org/teiid/transport/TestODBCSocketTransport.java +++ b/test-integration/common/src/test/java/org/teiid/transport/TestODBCSocketTransport.java @@ -55,10 +55,12 @@ import org.postgresql.core.v3.ExtendedQueryExectutorImpl; import org.teiid.adminapi.Model.Type; import org.teiid.adminapi.impl.ModelMetaData; +import org.teiid.adminapi.impl.SessionMetadata; import org.teiid.adminapi.impl.VDBMetaData; import org.teiid.common.buffer.BufferManagerFactory; import org.teiid.core.util.ObjectConverterUtil; import org.teiid.core.util.UnitTestUtil; +import org.teiid.deployers.PgCatalogMetadataStore; import org.teiid.jdbc.FakeServer; import org.teiid.jdbc.TestMMDatabaseMetaData; import org.teiid.net.socket.SocketUtil; @@ -644,6 +646,7 @@ private void connect(String database) throws SQLException { Statement s = conn.createStatement(); ResultSet rs = s.executeQuery("select '2011-01-01'::date"); rs.next(); + assertEquals("2011-01-01", rs.getString(1)); } /** @@ -751,13 +754,62 @@ private void connect(String database) throws SQLException { assertEquals(1, odbcServer.server.getDqp().getRequests().size()); } - @Test public void testSomething() throws Exception { + @Test public void testExportedKey() throws Exception { String sql = ObjectConverterUtil.convertFileToString(UnitTestUtil.getTestDataFile("exported-fk-query.txt")); Statement s = conn.createStatement(); s.execute(sql); ResultSet rs = s.getResultSet(); assertTrue(rs.next()); + assertEquals("STATUS_ID", rs.getString(4)); + assertFalse(rs.next()); } + + @Test public void testRegClass() throws Exception { + Statement s = conn.createStatement(); + ResultSet rs = s.executeQuery("select '\"pg_catalog.pg_class\"'::regclass"); + rs.next(); + int oid = rs.getInt(1); + rs = s.executeQuery("select oid from pg_class where relname='pg_class'"); + rs.next(); + int oid1 = rs.getInt(1); + assertEquals(oid, oid1); + } + + @Test public void testGeometry() throws Exception { + Statement s = conn.createStatement(); + s.execute("SELECT ST_GeomFromText('POLYGON ((40 0, 50 50, 0 50, 0 0, 40 0))')"); + ResultSet rs = s.getResultSet(); + rs.next(); + ResultSetMetaData rsmd = rs.getMetaData(); + assertEquals("geometry", rsmd.getColumnTypeName(1)); + assertEquals("00200000030000000000000001000000054044000000000000000000000000000040490000000000004049000000000000000000000000000040490000000000000000000000000000000000000000000040440000000000000000000000000000", rs.getString(1)); + } + + @Test public void testConstraintDef() throws Exception { + Statement s = conn.createStatement(); + s.execute("SELECT pg_get_constraintdef((select oid from pg_constraint where contype = 'f' and conrelid = (select oid from pg_class where relname = 'Functions')), true)"); + ResultSet rs = s.getResultSet(); + rs.next(); + assertEquals("FOREIGN KEY (VDBName,SchemaName) REFERENCES SYS.Schemas(VDBName,Name)", rs.getString(1)); + } + + @Test public void testXML() throws Exception { + Statement s = conn.createStatement(); + s.execute("SELECT xmlelement(name x)"); + ResultSet rs = s.getResultSet(); + rs.next(); + ResultSetMetaData rsmd = rs.getMetaData(); + assertEquals("xml", rsmd.getColumnTypeName(1)); + assertEquals(" ", rs.getString(1)); + } + + @Test public void testVersion() throws Exception { + Statement s = conn.createStatement(); + s.execute("SELECT version()"); + ResultSet rs = s.getResultSet(); + rs.next(); + assertEquals(PgCatalogMetadataStore.POSTGRESQL_VERSION, rs.getString(1)); + } } diff --git a/test-integration/common/src/test/resources/TestCase3473/testGetTables.expected b/test-integration/common/src/test/resources/TestCase3473/testGetTables.expected index 8d879dd59d..c79b328e1c 100644 --- a/test-integration/common/src/test/resources/TestCase3473/testGetTables.expected +++ b/test-integration/common/src/test/resources/TestCase3473/testGetTables.expected @@ -21,24 +21,29 @@ test SYSADMIN test SYSADMIN Usage SYSTEM TABLE true test SYSADMIN VDBResources SYSTEM TABLE true test SYSADMIN Views SYSTEM TABLE true +test pg_catalog geography_columns SYSTEM TABLE false test pg_catalog matpg_datatype SYSTEM TABLE false test pg_catalog matpg_relatt SYSTEM TABLE false test pg_catalog pg_am SYSTEM TABLE false test pg_catalog pg_attrdef SYSTEM TABLE false test pg_catalog pg_attribute SYSTEM TABLE false test pg_catalog pg_class SYSTEM TABLE false +test pg_catalog pg_constraint SYSTEM TABLE false test pg_catalog pg_database SYSTEM TABLE false test pg_catalog pg_description SYSTEM TABLE false test pg_catalog pg_index SYSTEM TABLE false +test pg_catalog pg_inherits SYSTEM TABLE false test pg_catalog pg_namespace SYSTEM TABLE false +test pg_catalog pg_prepared_xacts SYSTEM TABLE false test pg_catalog pg_proc SYSTEM TABLE false +test pg_catalog pg_stats SYSTEM TABLE false test pg_catalog pg_trigger SYSTEM TABLE false test pg_catalog pg_type SYSTEM TABLE false test pg_catalog pg_user SYSTEM TABLE