diff --git a/Npgsql.EntityFramework/Npgsql.EntityFramework.csproj b/Npgsql.EntityFramework/Npgsql.EntityFramework.csproj index 8c6e5dd0fa..b0599a4c54 100644 --- a/Npgsql.EntityFramework/Npgsql.EntityFramework.csproj +++ b/Npgsql.EntityFramework/Npgsql.EntityFramework.csproj @@ -9,6 +9,7 @@ Library Properties Npgsql.EntityFramework + Npgsql 512 true ..\Npgsql.snk @@ -97,6 +98,7 @@ + diff --git a/Npgsql.EntityFramework/Npgsql.EntityFrameworkLegacy.csproj b/Npgsql.EntityFramework/Npgsql.EntityFrameworkLegacy.csproj index 5ec3323653..965be3e111 100644 --- a/Npgsql.EntityFramework/Npgsql.EntityFrameworkLegacy.csproj +++ b/Npgsql.EntityFramework/Npgsql.EntityFrameworkLegacy.csproj @@ -9,6 +9,7 @@ Library Properties Npgsql.EntityFrameworkLegacy + Npgsql 512 true ..\Npgsql.snk diff --git a/Npgsql.EntityFramework/NpgsqlConnectionFactory.cs b/Npgsql.EntityFramework/NpgsqlConnectionFactory.cs new file mode 100755 index 0000000000..1d5e4994be --- /dev/null +++ b/Npgsql.EntityFramework/NpgsqlConnectionFactory.cs @@ -0,0 +1,21 @@ +using System.Data.Common; +using System.Data.Entity.Infrastructure; + +namespace Npgsql +{ + /// + /// Instances of this class are used to create DbConnection objects for Postgresql + /// + public class NpgsqlConnectionFactory : IDbConnectionFactory + { + /// + /// Creates a connection for Postgresql for the given connection string. + /// + /// The connection string. + /// An initialized DbConnection. + public DbConnection CreateConnection(string nameOrConnectionString) + { + return new NpgsqlConnection(nameOrConnectionString); + } + } +} diff --git a/Npgsql.EntityFramework/NpgsqlProviderManifest.cs b/Npgsql.EntityFramework/NpgsqlProviderManifest.cs index 0d4351522e..6ff4223075 100644 --- a/Npgsql.EntityFramework/NpgsqlProviderManifest.cs +++ b/Npgsql.EntityFramework/NpgsqlProviderManifest.cs @@ -16,7 +16,7 @@ namespace Npgsql internal class NpgsqlProviderManifest : DbXmlEnabledProviderManifest { public NpgsqlProviderManifest(string serverVersion) - : base(CreateXmlReaderForResource("NpgsqlProviderManifest.Manifest.xml")) + : base(CreateXmlReaderForResource("Npgsql.NpgsqlProviderManifest.Manifest.xml")) { } @@ -26,11 +26,11 @@ protected override XmlReader GetDbInformation(string informationType) if (informationType == StoreSchemaDefinition) { - xmlReader = CreateXmlReaderForResource("NpgsqlSchema.ssdl"); + xmlReader = CreateXmlReaderForResource("Npgsql.NpgsqlSchema.ssdl"); } else if (informationType == StoreSchemaMapping) { - xmlReader = CreateXmlReaderForResource("NpgsqlSchema.msl"); + xmlReader = CreateXmlReaderForResource("Npgsql.NpgsqlSchema.msl"); } if (xmlReader == null) diff --git a/Npgsql/Npgsql.csproj b/Npgsql/Npgsql.csproj index 999fbd150b..cfce0670cf 100644 --- a/Npgsql/Npgsql.csproj +++ b/Npgsql/Npgsql.csproj @@ -9,6 +9,7 @@ Library Properties Npgsql + Npgsql 512 true ..\Npgsql.snk diff --git a/Npgsql/Npgsql/NpgsqlCommand.Rewrite.cs b/Npgsql/Npgsql/NpgsqlCommand.Rewrite.cs index 0524aeebde..647e8bc363 100644 --- a/Npgsql/Npgsql/NpgsqlCommand.Rewrite.cs +++ b/Npgsql/Npgsql/NpgsqlCommand.Rewrite.cs @@ -558,7 +558,8 @@ private enum TokenType None, Quoted, Param, - Colon + Colon, + FullTextMatchOp } /// @@ -579,6 +580,7 @@ private void AppendCommandReplacingParameterValues(Stream dest, string src, int int currTokenBeg = begin; int currTokenLen = 0; Dictionary paramOrdinalMap = null; + int end = begin + length; if (prepare) { @@ -590,7 +592,7 @@ private void AppendCommandReplacingParameterValues(Stream dest, string src, int } } - for (int currCharOfs = begin ; currCharOfs < begin + length ; currCharOfs++) + for (int currCharOfs = begin ; currCharOfs < end ; currCharOfs++) { char ch = src[currCharOfs]; @@ -602,7 +604,7 @@ private void AppendCommandReplacingParameterValues(Stream dest, string src, int case TokenType.None : switch (ch) { - case '\'': + case '\'' : if (currTokenLen > 0) { dest.WriteString(src.Substring(currTokenBeg, currTokenLen)); @@ -615,7 +617,8 @@ private void AppendCommandReplacingParameterValues(Stream dest, string src, int break; - case ':': + case ':' : + if (currTokenLen > 0) { dest.WriteString(src.Substring(currTokenBeg, currTokenLen)); } @@ -627,20 +630,21 @@ private void AppendCommandReplacingParameterValues(Stream dest, string src, int break; - case '@': + case '<' : + case '@' : + if (currTokenLen > 0) { dest.WriteString(src.Substring(currTokenBeg, currTokenLen)); } - currTokenType = TokenType.Param; + currTokenType = TokenType.FullTextMatchOp; - currTokenBeg = currCharOfs + 1; - currTokenLen = 0; - paramMarker = '@'; + currTokenBeg = currCharOfs; + currTokenLen = 1; break; - default: + default : currTokenLen++; break; @@ -705,12 +709,12 @@ private void AppendCommandReplacingParameterValues(Stream dest, string src, int case TokenType.Quoted : switch (ch) { - case '\'': + case '\'' : currTokenLen++; break; - default: + default : if (currTokenLen > 1 && lastChar == '\'') { dest.WriteString(src.Substring(currTokenBeg, currTokenLen)); @@ -734,40 +738,47 @@ private void AppendCommandReplacingParameterValues(Stream dest, string src, int break; case TokenType.Colon : - switch (ch) + if (IsParamNameChar(ch)) { - case ':': - currTokenLen++; - - break; + currTokenType = TokenType.Param; - default: - if (currTokenLen == 1) - { - currTokenType = TokenType.Param; + currTokenBeg = currCharOfs; + currTokenLen = 0; + paramMarker = ':'; - currTokenBeg = currCharOfs; - currTokenLen = 0; - paramMarker = ':'; - } - else - { - dest.WriteString(src.Substring(currTokenBeg, currTokenLen)); + // Re-evaluate this character + goto ProcessCharacter; + } + else + { + // Demote to the unknown token type and continue. + currTokenType = TokenType.None; + currTokenLen++; + } - currTokenType = TokenType.None; + break; - currTokenBeg = currCharOfs; - currTokenLen = 0; - } + case TokenType.FullTextMatchOp : + if (lastChar == '@' && IsParamNameChar(ch)) + { + currTokenType = TokenType.Param; - // Re-evaluate this character - goto ProcessCharacter; + currTokenBeg = currCharOfs; + currTokenLen = 0; + paramMarker = '@'; + // Re-evaluate this character + goto ProcessCharacter; + } + else + { + // Demote to the unknown token type and continue. + currTokenType = TokenType.None; + currTokenLen++; } break; - } lastChar = ch; diff --git a/Npgsql/Npgsql/NpgsqlRowDescription.cs b/Npgsql/Npgsql/NpgsqlRowDescription.cs index 533a32cb25..e213e2de8c 100644 --- a/Npgsql/Npgsql/NpgsqlRowDescription.cs +++ b/Npgsql/Npgsql/NpgsqlRowDescription.cs @@ -32,6 +32,7 @@ using System.IO; using System.Text; using NpgsqlTypes; +using System.Text.RegularExpressions; namespace Npgsql { @@ -196,12 +197,26 @@ public int TryFieldIndex(string fieldName) public int FieldIndex(String fieldName) { int ret = -1; - if(field_name_index_table.TryGetValue(fieldName, out ret) || caseInsensitiveNameIndexTable.TryGetValue(fieldName, out ret)) + + if (field_name_index_table.TryGetValue(fieldName, out ret) + || caseInsensitiveNameIndexTable.TryGetValue(fieldName, out ret)) + return ret; + + string fieldNameUnderScore = ConvertToUnderscore(fieldName); + if(field_name_index_table.TryGetValue(fieldNameUnderScore, out ret) + || caseInsensitiveNameIndexTable.TryGetValue(fieldNameUnderScore, out ret)) return ret; - else if(_compatVersion < GET_ORDINAL_THROW_EXCEPTION) + + else if (_compatVersion < GET_ORDINAL_THROW_EXCEPTION) return -1; else throw new IndexOutOfRangeException("Field not found"); + + } + + private string ConvertToUnderscore(string val) + { + return Regex.Replace(val, @"(\p{Ll})(\p{Lu})", "$1_$2"); } } diff --git a/Npgsql/Npgsql/NpgsqlSchema.cs b/Npgsql/Npgsql/NpgsqlSchema.cs index a7d8ce292c..67422f6e59 100644 --- a/Npgsql/Npgsql/NpgsqlSchema.cs +++ b/Npgsql/Npgsql/NpgsqlSchema.cs @@ -411,7 +411,7 @@ from pg_catalog.pg_constraint pgc internal static DataTable GetDataSourceInformation() { DataSet ds = new DataSet(); - using (Stream xmlStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Npgsql.NpgsqlMetaData.xml")) + using (Stream xmlStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Npgsql.Npgsql.NpgsqlMetaData.xml")) { ds.ReadXml(xmlStream); } diff --git a/Npgsql/policy.2.0.Npgsql.config b/Npgsql/policy.2.0.Npgsql.config new file mode 100644 index 0000000000..bc4a8f7e3d --- /dev/null +++ b/Npgsql/policy.2.0.Npgsql.config @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/Npgsql/policyFileBuild.bat b/Npgsql/policyFileBuild.bat new file mode 100644 index 0000000000..a95296b14c --- /dev/null +++ b/Npgsql/policyFileBuild.bat @@ -0,0 +1,28 @@ +@if "%WindowsSdkDir%"=="" goto NO_WIN_SDK + +@setlocal + +@set net20out=bin\policies\net20 +@set net40out=bin\policies\net40 + +@if not exist %net20out% mkdir %net20out% +@if not exist %net40out% mkdir %net40out% + +@set aldir=%WindowsSdkDir_35% + +:: If dev env is VS2010, then another path should be used. +@if "%aldir%"=="" set aldir=%WindowsSDKDir%\Bin + +@"%aldir%\al.exe" /nologo /link:policy.2.0.Npgsql.config /out:%net20out%\policy.2.0.Npgsql.dll /keyfile:Npgsql\Npgsql.snk +@"%aldir%\NETFX 4.0 Tools\al.exe" /nologo /link:policy.2.0.Npgsql.config /out:%net40out%\policy.2.0.Npgsql.dll /keyfile:Npgsql\Npgsql.snk +@goto :EOF + +:NO_WIN_SDK +@echo ========================================================== +@echo ERROR: +@echo Please make sure that an environment variable WindowsSdkDir_35 is set. +@echo This variable should point to the SDK dir which contains al.exe for clr 2.0. +@echo You can set this variable with launching command shell via Windows SDK Comamnd +@echo Prompt or Visual Studio Command Prompt. +@echo ========================================================== +@pause \ No newline at end of file diff --git a/tests/App.config b/tests/App.config new file mode 100644 index 0000000000..77cb73eb24 --- /dev/null +++ b/tests/App.config @@ -0,0 +1,18 @@ + + + + +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/CommandTests.cs b/tests/CommandTests.cs index 7b66d35227..3ddc35fea8 100644 --- a/tests/CommandTests.cs +++ b/tests/CommandTests.cs @@ -3481,5 +3481,32 @@ public void DataTypeTests() Assert.AreEqual(typeof(NpgsqlTimeTZ), result.GetType()); */ } + + [Test] + // Target NpgsqlCommand.AppendCommandReplacingParameterValues()'s handling of operator @@. + public void Operator_At_At_RewriteTest() + { + NpgsqlCommand cmd = new NpgsqlCommand("SELECT to_tsvector('fat cats ate rats') @@ to_tsquery('cat & rat')", Conn); + + Assert.IsTrue((bool)cmd.ExecuteScalar()); + } + + [Test] + // Target NpgsqlCommand.AppendCommandReplacingParameterValues()'s handling of operator @>. + public void Operator_At_GT_RewriteTest() + { + NpgsqlCommand cmd = new NpgsqlCommand("SELECT 'cat'::tsquery @> 'cat & rat'::tsquery", Conn); + + Assert.IsFalse((bool)cmd.ExecuteScalar()); + } + + [Test] + // Target NpgsqlCommand.AppendCommandReplacingParameterValues()'s handling of operator <@. + public void Operator_LT_At_RewriteTest() + { + NpgsqlCommand cmd = new NpgsqlCommand("SELECT 'cat'::tsquery <@ 'cat & rat'::tsquery", Conn); + + Assert.IsTrue((bool)cmd.ExecuteScalar()); + } } } diff --git a/tests/EntityFrameworkTests.cs b/tests/EntityFrameworkTests.cs new file mode 100644 index 0000000000..0cbd04851f --- /dev/null +++ b/tests/EntityFrameworkTests.cs @@ -0,0 +1,167 @@ +using Npgsql; +using NpgsqlTests; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Data.Entity; +using System.Linq; +using System.Text; + +namespace NpgsqlTests +{ + public class EntityFrameworkTests : TestBase + { + public EntityFrameworkTests(string backendVersion) : base(backendVersion) { } + + protected override void SetUp() + { + base.SetUp(); + // If this is the first (or only) test being run, the connection has already been opened + // in the fixture setup. Save the extra connecting time. + ExecuteNonQuery(@" -- Table: posts + -- DROP TABLE posts; + + CREATE TABLE posts + ( + id serial NOT NULL, + title character varying, + body character varying, + user_name character varying, + search_vector tsvector, + CONSTRAINT posts_pkey PRIMARY KEY (id) + ) + WITH ( + OIDS=FALSE + ); + + -- Index: posts_search_idx + + -- DROP INDEX posts_search_idx; + + CREATE INDEX posts_search_idx + ON posts USING gin (search_vector); + + -- Trigger: posts_vector_update on posts + + -- DROP TRIGGER posts_vector_update ON posts; + + CREATE TRIGGER posts_vector_update + BEFORE INSERT OR UPDATE ON posts + FOR EACH ROW + EXECUTE PROCEDURE tsvector_update_trigger('search_vector', 'pg_catalog.english', 'title', 'body'); + "); + + ExecuteNonQuery("INSERT INTO posts (title, body, user_name) VALUES ('Postgres is awesome', '', 'Clark Kent')"); + ExecuteNonQuery("INSERT INTO posts (title, body, user_name) VALUES ('How postgres is differente from MySQL', '', 'Lois Lane')"); + ExecuteNonQuery("INSERT INTO posts (title, body, user_name) VALUES ('Tips for Mysql', '', 'Bruce Wayne')"); + ExecuteNonQuery("INSERT INTO posts (title, body, user_name) VALUES ('SECRET', 'Postgres for the win', 'Dick Grayson')"); + ExecuteNonQuery("INSERT INTO posts (title, body, user_name) VALUES ('Oracle acquires some other database', 'Mysql but no postgres' , 'Oliver Queen')"); + ExecuteNonQuery("INSERT INTO posts (title, body, user_name) VALUES ('No Database', 'Nothing to see here', 'Kyle Ryner')"); + } + + protected override void TearDown() + { + ExecuteNonQuery("DROP TABLE posts"); + base.TearDown(); + } + + [Test] + public void FullTextSearchSimpleTest() + { + var conn = Npgsql.NpgsqlFactory.Instance.CreateConnection(); + conn.ConnectionString = ConnectionString; + using (var ctx = new DbContext(conn, true)) + { + var query = @"select * + from posts + where search_vector @@ to_tsquery('english', @p0) + order by ts_rank_cd(search_vector, to_tsquery('english', @p0)) desc"; + var p = "postgres"; + var posts = ctx.Database.SqlQuery(query, p).ToList(); + + Assert.AreEqual(4, posts.Count); + } + } + + [Test] + public void FullTextSearchAndTest() + { + var conn = Npgsql.NpgsqlFactory.Instance.CreateConnection(); + conn.ConnectionString = ConnectionString; + using (var ctx = new DbContext(conn, true)) + { + var query = @"select * + from posts + where search_vector @@ to_tsquery('english', @p0) + order by ts_rank_cd(search_vector, to_tsquery('english', @p0)) desc"; + var p = "postgres & mysql"; + var posts = ctx.Database.SqlQuery(query, p).ToList(); + + Assert.AreEqual(2, posts.Count); + } + } + + [Test] + public void FullTextSearchOrTest() + { + var conn = Npgsql.NpgsqlFactory.Instance.CreateConnection(); + conn.ConnectionString = ConnectionString; + using (var ctx = new DbContext(conn, true)) + { + var query = @"select * + from posts + where search_vector @@ to_tsquery('english', @p0) + order by ts_rank_cd(search_vector, to_tsquery('english', @p0)) desc"; + var p = "postgres | mysql"; + var posts = ctx.Database.SqlQuery(query, p).ToList(); + + Assert.AreEqual(5, posts.Count); + } + } + + [Test] + public void DbContextSqlQueryMapWithUnderscoreColumnNames() + { + var conn = Npgsql.NpgsqlFactory.Instance.CreateConnection(); + conn.ConnectionString = ConnectionString; + using (var ctx = new TestDbContext(conn, true)) + { + var query = @"select * from posts"; + var posts = ctx.Database.SqlQuery(query).ToList(); + + Assert.AreEqual(6, posts.Count); + Assert.AreEqual("Clark Kent", posts.FirstOrDefault().UserName); + } + } + } + + public class TestDbContext : DbContext + { + public TestDbContext(DbConnection existingConnection, bool contextOwnsConnection) + : base(existingConnection, contextOwnsConnection) + { + Database.SetInitializer(null); + } + + public DbSet Posts { get; set; } + + protected override void OnModelCreating(DbModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + modelBuilder.Entity().ToTable("posts", "public"); + modelBuilder.Entity().Property(c => c.Id).HasColumnName("id").IsOptional(); + modelBuilder.Entity().Property(c => c.Title).HasColumnName("title").IsOptional(); + modelBuilder.Entity().Property(c => c.Body).HasColumnName("body").IsOptional(); + modelBuilder.Entity().Property(c => c.UserName).HasColumnName("user_name").IsOptional(); + } + } + + public class Post + { + public int Id { get; set; } + public string Title { get; set; } + public string Body { get; set; } + public string UserName { get; set; } + } +} diff --git a/tests/NpgsqlTests.csproj b/tests/NpgsqlTests.csproj index 1b380a8632..37b50fc716 100644 --- a/tests/NpgsqlTests.csproj +++ b/tests/NpgsqlTests.csproj @@ -131,7 +131,17 @@ 4 v4.5 + + false + + + + + + + ..\packages\EntityFramework.6.0.1\lib\net40\EntityFramework.dll + ..\packages\NUnit.2.6.2\lib\nunit.framework.dll @@ -170,11 +180,13 @@ + + Always @@ -214,6 +226,10 @@ + + {3ec85cba-5b79-11e3-8104-0022198ab089} + Npgsql.EntityFramework + {9D13B739-62B1-4190-B386-7A9547304EB3} Npgsql2012 diff --git a/tests/packages.config b/tests/packages.config index 5c3ca54dd7..fbdacc1a3b 100644 --- a/tests/packages.config +++ b/tests/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file