Reengineer SQL into SubSonic LINQ - subsonic

I have 11 records in the Menu table, only 1 with a PageID set however if I use
var test = Menu.All().Where(
x => x.WebPages.Any(
pages => pages.Roles.Contains(Roles.GetRolesForUser()[0])
I get 11 records as the SQL run is this
SELECT [t0].[CategoryID], [t0].[CreatedBy], [t0].[CreatedOn],
[t0].[ID], [t0].[ImageID], [t0].[ImageIDHover], [t0].[Locale],
[t0].[ModifiedBy], [t0].[ModifiedOn], [t0].[OrderID], [t0].[PageID],
[t0].[ParentID], [t0].[Title], [t0].[URL], [t0].[Visible]
FROM [dbo].[Menu] AS t0
WHERE EXISTS(
SELECT NULL
FROM [dbo].[WebPage] AS t1
WHERE ([t1].[Roles] LIKE '%' + 'User' + '%')
)
If I run this I get the 1 record
var test = Menu.All().Where(
x => x.WebPages.Any(
pages => pages.Roles.Contains(
Roles.GetRolesForUser()[0]) && pages.ID == x.PageID));
The SQL for this is
SELECT [t0].[CategoryID], [t0].[CreatedBy], [t0].[CreatedOn],
[t0].[ID], [t0].[ImageID], [t0].[ImageIDHover], [t0].[Locale],
[t0].[ModifiedBy], [t0].[ModifiedOn], [t0].[OrderID], [t0].[PageID],
[t0].[ParentID], [t0].[Title], [t0].[URL], [t0].[Visible]
FROM [dbo].[Menu] AS t0
WHERE EXISTS (
SELECT NULL
FROM [dbo].[WebPage] AS t1
WHERE (([t1].[Roles] LIKE '%' + 'User' + '%') AND
([t1].[ID] = [t0].[PageID]))
)
The problem with Any() is that in the SQL as long as one record exits, doesn't matter which record it will return data.
I think effectively I am wanting an UNION SQL like below but I don't know how I re-engineer that into C#/Subsonic
select m.* from menu m where pageid is null
union
select m.* from menu m
join webpage p
on p.id = m.pageid
where p.roles like '%User%'
I want to return all menu records and for those with a PageID set that the corresponding WebPage has the user's role in it. If the user's role is not in the WebPage then I don't want to see it in my results.
Here are my cutodwn classes that Subsonic generates
public partial class Menu: IActiveRecord
{
public int ID {get; set;}
public int CategoryID {get;set;}
public bool Visible {get;set;}
public int PageID {get;set;}
public IQueryable<WebPage> WebPages
{
get
{
var repo=NorthCadburyWebsite.Models.WebPage.GetRepo();
return from items in repo.GetAll()
where items.ID == _PageID
select items;
}
}
}
public partial class WebPage: IActiveRecord
{
public int ID {get;set;}
public string Roles {get;set;}
}

It sure seems like you could just add an alternative test to the where clause to do this.
var test = Menu.All()
.Where(x => x.PageID == null
|| x.WebPages
.Any(pages => pages.Roles.Contains(Roles.GetRolesForUser()[0])
&& pages.ID == x.PageID));
EDIT:
Try using the the Intersect method to see if there is an overlap between the roles in the DB and the roles for the user.
var test = Menu.All()
.Where(x => x.PageID == null
|| x.WebPages
.Any(pages => pages.Roles.Intersect(Roles.GetRolesForUser().Split(','))
&& pages.ID == x.PageID));

Related

SQL Parser Visitor + Metabase + Presto

I'm facing what seems to be a quite easy problem, but I'm not able to put my head around the problem to find a suitable solution.
Problem:
I need to append the schema into my SQL statement, in a "weird"(with schema in double quotes) way.
FROM "SCHEMA".tableB tableB
LEFT JOIN "SCHEMA".tableC tableC
Context
Basically, we are hosting and exposing a Metabase tool that will connect and perform query on our Hive database using Presto SQL.
Metabase allow the customer to write SQL statements and some customers, they just don't type the schema on statements. Today we are throwing and error for those queries, but I could easily retrieve the schema value from the Authorization header, since in our multi-tenant product the schema is the tenant id where this user is logged, and with that information in hands, I could append to the customer SQL statement and avoid the error.
Imagine that the customer typed the follow statement:
SELECT tableA.*
, (tableA.valorfaturado + tableA.valorcortado) valorpedido
FROM (SELECT from_unixtime(tableB.datacorte / 1000) datacorte
, COALESCE((tableB.quantidadecortada * tableC.preco), 0) valorcortado
, COALESCE((tableB.quantidade * tableC.preco), 0) valorfaturado
, tableB.quantidadecortada
FROM tableB tableB
LEFT JOIN tableC tableC
ON tableC.numeropedido = tableB.numeropedido
AND tableC.codigoproduto = tableB.codigoproduto
AND tableC.codigofilial = tableB.codigofilial
LEFT JOIN tableD tableD
ON tableD.numero = tableB.numeropedido
WHERE (CASE
WHEN COALESCE(tableB.codigofilial, '') = '' THEN
tableD.codigofilial
ELSE
tableB.codigofilial
END) = '10'
AND from_unixtime(tableB.datacorte / 1000) BETWEEN from_iso8601_timestamp('2020-07-01T03:00:00.000Z') AND from_iso8601_timestamp('2020-08-01T02:59:59.999Z')) tableA
ORDER BY datacorte
I should convert this into (adding the "SCHEMA"):
SELECT tableA.*
, (tableA.valorfaturado + tableA.valorcortado) valorpedido
FROM (SELECT from_unixtime(tableB.datacorte / 1000) datacorte
, COALESCE((tableB.quantidadecortada * tableC.preco), 0) valorcortado
, COALESCE((tableB.quantidade * tableC.preco), 0) valorfaturado
, tableB.quantidadecortada
FROM "SCHEMA".tableB tableB
LEFT JOIN "SCHEMA".tableC tableC
ON tableC.numeropedido = tableB.numeropedido
AND tableC.codigoproduto = tableB.codigoproduto
AND tableC.codigofilial = tableB.codigofilial
LEFT JOIN "SCHEMA".tableD tableD
ON tableD.numero = tableB.numeropedido
WHERE (CASE
WHEN COALESCE(tableB.codigofilial, '') = '' THEN
tableD.codigofilial
ELSE
tableB.codigofilial
END) = '10'
AND from_unixtime(tableB.datacorte / 1000) BETWEEN from_iso8601_timestamp('2020-07-01T03:00:00.000Z') AND from_iso8601_timestamp('2020-08-01T02:59:59.999Z')) tableA
ORDER BY datacorte
Still trying to find a solution that uses only presto-parser and Visitor + Instrumentation solution.
Also, I know about JSQLParser and I tried, but I alway come back to try to find a "plain" solution scared that JSQLParser will not be able to support all the Presto/Hive queries, that are a little bit different than standard SQL;
I create a little project on GitHub with test case to validate..
https://github.com/genyherrera/prestosqlerror
But for those that don't want to clone a repository, here are the classes and dependencies:
import java.util.Optional;
import com.facebook.presto.sql.SqlFormatter;
import com.facebook.presto.sql.parser.ParsingOptions;
import com.facebook.presto.sql.parser.SqlParser;
public class SchemaAwareQueryAdapter {
// Inspired from
// https://github.com/prestodb/presto/tree/master/presto-parser/src/test/java/com/facebook/presto/sql/parser
private static final SqlParser SQL_PARSER = new SqlParser();
public String rewriteSql(String sqlStatement, String schemaId) {
com.facebook.presto.sql.tree.Statement statement = SQL_PARSER.createStatement(sqlStatement, ParsingOptions.builder().build());
SchemaAwareQueryVisitor visitor = new SchemaAwareQueryVisitor(schemaId);
statement.accept(visitor, null);
return SqlFormatter.formatSql(statement, Optional.empty());
}
}
public class SchemaAwareQueryVisitor extends DefaultTraversalVisitor<Void, Void> {
private String schemaId;
public SchemaAwareQueryVisitor(String schemaId) {
super();
this.schemaId = schemaId;
}
/**
* The customer can type:
* [table name]
* [schema].[table name]
* [catalog].[schema].[table name]
*/
#Override
protected Void visitTable(Table node, Void context) {
List<String> parts = node.getName().getParts();
// [table name] -> is the only one we need to modify, so let's check by parts.size() ==1
if (parts.size() == 1) {
try {
Field privateStringField = Table.class.getDeclaredField("name");
privateStringField.setAccessible(true);
QualifiedName qualifiedName = QualifiedName.of("\""+schemaId+"\"",node.getName().getParts().get(0));
privateStringField.set(node, qualifiedName);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
throw new SecurityException("Unable to execute query");
}
}
return null;
}
}
import static org.testng.Assert.assertEquals;
import org.gherrera.prestosqlparser.SchemaAwareQueryAdapter;
import org.testng.annotations.Test;
public class SchemaAwareTest {
private static final String schemaId = "SCHEMA";
private SchemaAwareQueryAdapter adapter = new SchemaAwareQueryAdapter();
#Test
public void testAppendSchemaA() {
String sql = "select * from tableA";
String bound = adapter.rewriteSql(sql, schemaId);
assertEqualsFormattingStripped(bound,
"select * from \"SCHEMA\".tableA");
}
private void assertEqualsFormattingStripped(String sql1, String sql2) {
assertEquals(sql1.replace("\n", " ").toLowerCase().replace("\r", " ").replaceAll(" +", " ").trim(),
sql2.replace("\n", " ").toLowerCase().replace("\r", " ").replaceAll(" +", " ").trim());
}
}
<dependencies>
<dependency>
<groupId>com.facebook.presto</groupId>
<artifactId>presto-parser</artifactId>
<version>0.229</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.10</version>
<scope>test</scope>
</dependency>
</dependencies>
PS: I was able to add the schema without the doubles quotes, but them I got into identifiers must not start with a digit; surround the identifier with double quotes error. Basically this error comes from SqlParser$PostProcessor.exitDigitIdentifier(...) method..
Thanks
I was able to find a solution for my case, either way will share on Presto Slack my finding to see if that is expected behavior.
So, if you want to append with double quote your schema, you will need to create your own Vistor class and you'll need to override the method visitTable and when you Qualify the name of your table with schema, (here's the tick), pass the schema as UPPERCASE, so it will not match the regex pattern on class SqlFormatter on method formatName and it will add the double-quote..
public class SchemaAwareQueryVisitor extends DefaultTraversalVisitor<Void, Void> {
private String schemaId;
public SchemaAwareQueryVisitor(String schemaId) {
super();
this.schemaId = schemaId;
}
#Override
protected Void visitTable(Table node, Void context) {
try {
Field privateStringField = Table.class.getDeclaredField("name");
privateStringField.setAccessible(true);
QualifiedName qualifiedName = QualifiedName.of(schemaId, node.getName().getParts().get(0));
privateStringField.set(node, qualifiedName);
} catch (NoSuchFieldException
| SecurityException
| IllegalArgumentException
| IllegalAccessException e) {
throw new SecurityException("Unable to execute query");
}
return null;
}
}

Unable to apply sorting in MVC 5

I'm trying to apply sorting in my MVC 5 application. I used the code here
to apply sorting. Unfortunately, its not working and it won't sort. Am I missing something? All datatypes I used are strings btw.
Here is the code:
//Controller
public ActionResult Index(string sort)
{
ViewBag.ExtSortParm = String.IsNullOrEmpty(sort) ? "ext_desc" : "";
ViewBag.DtsSortParm = sort == "DTS" ? "dts_desc" : "DTS";
var sales = from s in db.Sales1 select s;
switch (sort)
{
case "ext_desc":
sales = sales.OrderByDescending(s => s.ExtSerial);
break;
case "DTS":
sales = sales.OrderBy(s => s.DTS);
break;
case "dts_desc":
sales = sales.OrderByDescending(s => s.DTS);
break;
default:
sales = sales.OrderBy(s => s.ExtSerial);
break;
}
return View(db.Sales1.ToList());
}
And my View applied:
#Html.ActionLink("ExtSerial", "Index", new { sortOrder = ViewBag.ExtSortParm })
#Html.ActionLink("DTS", "Index", new { sortOrder = ViewBag.DtsSortParm })
Was there something I missed? DTS is a date by the way just in string type. Here is an example value: 5/11/2015 5:29:56 AM
db is my database & Sales1 is my SalesEntity
You are sorting your collection but he following line returns the original unsorted collection
return View(db.Sales1.ToList());
You need to return the sorted collection to the view using
return View(sales.ToList());

Is it possible to do paging with JoinSqlBuilder?

I have a pretty normal join that I create via JoinSqlBuilder
var joinSqlBuilder = new JoinSqlBuilder<ProductWithManufacturer, Product>()
.Join<Product, Manufacturer>(sourceColumn: p => p.ManufacturerId,
destinationColumn: mf => mf.Id,
sourceTableColumnSelection: p => new { ProductId = p.Id, ProductName = p.Name },
destinationTableColumnSelection: m => new { ManufacturerId = m.Id, ManufacturerName = m.Name })
Of course, the join created by this could potentially return a lot of rows, so I want to use paging - preferably on the server-side. However, I cannot find anything in the JoinSqlBuilder which would let me do this? Am I missing something or does JoinSqlBuilder not have support for this (yet)?
If you aren't using MS SQL Server I think the following will work.
var sql = joinSqlBuilder.ToSql();
var data = this.Select<ProductWithManufacturer>(
q => q.Select(sql)
.Limit(skip,rows)
);
If you are working with MS SQL Server, it will most likely blow up on you. I am working to merge a more elegant solution similar to this into JoinSqlBuilder. The following is a quick and dirty method to accomplish what you want.
I created the following extension class:
public static class Extension
{
private static string ToSqlWithPaging<TResult, TTarget>(
this JoinSqlBuilder<TResult, TTarget> bldr,
string orderColumnName,
int limit,
int skip)
{
var sql = bldr.ToSql();
return string.Format(#"
SELECT * FROM (
SELECT ROW_NUMBER() OVER (ORDER BY [{0}]) As RowNum, *
FROM (
{1}
)as InnerResult
)as RowConstrainedResult
WHERE RowNum > {2} AND RowNum <= {3}
", orderColumnName, sql, skip, skip + limit);
}
public static string ToSqlWithPaging<TResult, TTarget>(
this JoinSqlBuilder<TResult, TTarget> bldr,
Expression<Func<TResult, object>> orderSelector,
int limit,
int skip)
{
var member = orderSelector.Body as MemberExpression;
if (member == null)
throw new ArgumentException(
"TResult selector refers to a non member."
);
var propInfo = member.Member as PropertyInfo;
if (propInfo == null)
throw new ArgumentException(
"TResult selector refers to a field, it must be a property."
);
var orderSelectorName = propInfo.Name;
return ToSqlWithPaging(bldr, orderSelectorName, limit, skip);
}
}
It is applied as follows:
List<Entity> GetAllEntities(int limit, int skip)
{
var bldr = GetJoinSqlBuilderFor<Entity>();
var sql = bldr.ToSqlWithPaging(
entity => entity.Id,
limit,
skip);
return this.Db.Select<Entity>(sql);
}

How to check exist item in cart and update it to 1 if user add it again?

i have trouble with JPQL statement, i created NameQuery followed:
#NamedQuery(name = "Cart.findExistCart", query = "SELECT c FROM Cart c WHERE c.cartPK.userid = :userid AND c.cartPK.itemid = :itemid "),
in stateless bean i create one method to active this query followed:
public Cart getUserItemCart(int userid,int itemid){
Query query = em.createNamedQuery("Cart.findExistCart");
// query.setParameter("userid", userid);
query.setParameter("itemid", itemid);
query.setParameter("userid", userid);
return (Cart) query.getSingleResult();
}
after that in JSF Managed Bean i create one method test if user id and item id are exist in DB it will print user id and itemid to server:
public void addtoCart(Items item){
this.items = item;
int userid = getMemberLoginController().getUser().getUserid().intValue();
int itemid = getItems().getItemid().intValue();
if (cartDAO.getUserItemCart(userid, itemid) != null ){
// cart = cartDAO.getUserItemCart(userid, itemid);
cartPk.setUserid(userid);
cartPk.setItemid(itemid);
cart.setCartPK(cartPk);
cart = cartDAO.getUserItemCart(userid, itemid);
System.out.println(cart.getCartQuantity());
}
but when client add item A into db and then add item B into db, and once again add item A into DB , it don't print item id of item id, it print item id of item B. Why?
if i create select statement findByItemid, it work well if i do upon requirement it mean when click add A then click add B , and add A again it print id 1-2-1, but if i use select statement find Cart by ItemId and UserId it not work, it will print itemid is 1-2-2
What's happen with it?
you can delete 2 line
cartPk.setUserid(userid);
cartPk.setItemid(itemid);
i think when you assign cart = cartDAO.getUserItemCart(userid, itemid);
the userid and itemid are have value.

Anonymous type and getting values out side of method scope

I am building an asp.net site in .net framework 4.0, and I am stuck at the method that supposed to call a .cs class and get the query result back here is my method call and method
1: method call form aspx.cs page:
helper cls = new helper();
var query = cls.GetQuery(GroupID,emailCap);
2: Method in helper class:
public IQueryable<VariablesForIQueryble> GetQuery(int incomingGroupID, int incomingEmailCap)
{
var ctx = new some connection_Connection();
ObjectSet<Members1> members = ctx.Members11;
ObjectSet<groupMember> groupMembers = ctx.groupMembers;
var query = from m in members
join gm in groupMembers on m.MemberID equals gm.MemID
where (gm.groupID == incomingGroupID) && (m.EmailCap == incomingEmailCap)
select new VariablesForIQueryble(m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap);
//select new {m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap};
return query ;
}
I tried the above code with IEnumerable too without any luck. This is the code for class VariablesForIQueryble:
3:Class it self for taking anonymouse type and cast it to proper types:
public class VariablesForIQueryble
{
private int _emailCap;
public int EmailCap
{
get { return _emailCap; }
set { _emailCap = value; }
}`....................................
4: and a constructor:
public VariablesForIQueryble(int memberID, string memberFirst, string memberLast, string memberEmail, int? validEmail, int? emailCap)
{
this.EmailCap = (int) emailCap;
.........................
}
I can't seem to get the query result back, first it told me anonymous type problem, I made a class after reading this: link text; and now it tells me constructors with parameters not supported. Now I am an intermediate developer, is there an easy solution to this or do I have to take my query back to the .aspx.cs page.
If you want to project to a specific type .NET type like this you will need to force the query to actually happen using either .AsEnumerable() or .ToList() and then use .Select() against linq to objects.
You could leave your original anonymous type in to specify what you want back from the database, then call .ToList() on it and then .Select(...) to reproject.
You can also clean up your code somewhat by using an Entity Association between Groups and Members using a FK association in the database. Then the query becomes a much simpler:
var result = ctx.Members11.Include("Group").Where(m => m.Group.groupID == incomingGroupID && m.EmailCap == incomingEmailCap);
You still have the issue of having to do a select to specify which columns to return and then calling .ToList() to force execution before reprojecting to your new type.
Another alternative is to create a view in your database and import that as an Entity into the Entity Designer.
Used reflection to solve the problem:
A: Query, not using custom made "VariablesForIQueryble" class any more:
//Method in helper class
public IEnumerable GetQuery(int incomingGroupID, int incomingEmailCap)
{
var ctx = new some_Connection();
ObjectSet<Members1> members = ctx.Members11;
ObjectSet<groupMember> groupMembers = ctx.groupMembers;
var query = from m in members
join gm in groupMembers on m.MemberID equals gm.MemID
where ((gm.groupID == incomingGroupID) && (m.EmailCap == incomingEmailCap)) //select m;
select new { m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap };
//select new VariablesForIQueryble (m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap);
//List<object> lst = new List<object>();
//foreach (var i in query)
//{
// lst.Add(i.MemberEmail);
//}
//return lst;
//return query.Select(x => new{x.MemberEmail,x.MemberID,x.ValidEmail,x.MemberFirst,x.MemberLast}).ToList();
return query;
}
B:Code to catch objects and conversion of those objects using reflection
helper cls = new helper();
var query = cls.GetQuery(GroupID,emailCap);
if (query != null)
{
foreach (var objRow in query)
{
System.Type type = objRow.GetType();
int memberId = (int)type.GetProperty("MemberID").GetValue(objRow, null);
string memberEmail = (string)type.GetProperty("MemberEmail").GetValue(objRow, null);
}
else
{
something else....
}

Resources