Getting stackoverflow on recursion - dart

I want to generate INSERT statement from JSON. The problem that I have problem with recursion (in section where I am getting nested elements). it's seems that I am getting SO.
import 'dart:convert';
main() {
Map jsonMap = jsonDecode(
'{"id":"1","name":"sample","Lots":[{"id":"1","title":"books","objs":[{"book":"name"}]}]}');
KVToTableInsert(String tablename, Map jsonMap) {
List<String> insertNoticeKeys = [];
List<String> insertNoticeValues = [];
jsonMap.forEach((key, value) {
if (value is List) // nested
{
KVToTableInsert(key, jsonMap); // if comment this line all work
} else {
insertNoticeKeys.add(key);
insertNoticeValues.add(value);
}
});
String sql = "INSERT INTO $tablename (" +
insertNoticeKeys.map((e) => '"$e"').join(', ') +
") VALUES (" +
insertNoticeValues.map((e) => "'$e'").join(', ') +
")";
print(sql);
}
KVToTableInsert("RootTable", jsonMap);
}
I expected to get 3 INSERT statment:
INSERT INTO RootTable ("id", "name") VALUES ('1', 'sample')
INSERT INTO Lots ("id", "title") VALUES ('1', 'books')
INSERT INTO Objs ("book") VALUES ('name')
But I am getting crush on this app.

There was a slight problem in your code, basically in this part:
if (value is List){
KVToTableInsert(key, jsonMap); // if comment this line all work
}
If you closely, take a look at your KVToTableInsert, you're passing arguments Map and not list. Which fails your recursion.
What you need to do is to pass the item of the array, into your KVToTableInsert, and this will work as expected
FINAL SOLUTION
// taking your jsonMap directly, and not decoding it
void main() {
Map jsonMap = {
"id":"1",
"name":"sample",
"Lots":[
{
"id": "1",
"title":"books",
"objs":[
{
"book":"name"
}
]
}
]
};
void KVToTableInsert(tablename, jsonMap){
List<String> insertNoticeKeys = [];
List<String> insertNoticeValues = [];
jsonMap.forEach((key,value){
if (value is List)
{
// Here is the magic, you need to iterate over your list, and pass MAP, not LIST
value.forEach((item){ KVToTableInsert(key, item);});
} else {
insertNoticeKeys.add(key);
insertNoticeValues.add(value);
}
});
String sql = "INSERT INTO $tablename (" +
insertNoticeKeys.map((e) => '"$e"').join(', ') +
") VALUES (" +
insertNoticeValues.map((e) => "'$e'").join(', ') +
")";
print(sql);
}
KVToTableInsert("Rootable", jsonMap);
}
OUTPUT
INSERT INTO objs ("book") VALUES ('name')
INSERT INTO Lots ("id", "title") VALUES ('1', 'books')
INSERT INTO Rootable ("id", "name") VALUES ('1', 'sample')
Also, look at the Dart Programming Language Naming Conventions, it is a good practise to use lowerCamelCase for your function/method name.

Related

Combining multiple items by condition within a single list

As in Dart you can combine several list items into one according to the following condition:
Given List<String> arr = ['2','3','b','*','4','5','6','-','3','4'];
Get arr = ['23','b','*','456','-','34'];
The list is unknown in advance. It can be of any length and with any sequence of characters. And need to combine only the lines that have numbers in them.
I would be grateful for any suggestions.
You are not describing what should happen if there are multiple special characters or letters. So I have made my example so it will only combine numbers:
void main() {
final arr = ['2', '3', 'b', '*', '4', '5', '6', '-', '3', '4'];
print(combine(arr)); // [23, b, *, 456, -, 34]
}
List<String> combine(List<String> input) {
final output = <String>[];
final buffer = StringBuffer();
for (final string in input) {
if (int.tryParse(string) == null) {
if (buffer.isNotEmpty) {
output.add(buffer.toString());
buffer.clear();
}
output.add(string);
} else {
buffer.write(string);
}
}
if (buffer.isNotEmpty) {
output.add(buffer.toString());
}
return output;
}
You can use ''.join() here:
arr = [''.join(arr[0:2]), arr[2], arr[3], ''.join(arr[4:7]), arr[7], ''.join(arr[8:10])]
If you only want to have a condition where you only join numerical values then you can add a for loop beforehand.

List to quoted string

I need to convert List to quoted string. My code is:
void main() {
List<String> list = ["aa", "bb", "cc"];
String str = "INSERT INTO (" + list.toString() + ") VALUES (...) ";
print(str);
}
output:
INSERT INTO ([aa, bb, cc]) VALUES (...)
But I need:
INSERT INTO ("aa", "bb", "cc") VALUES (...)
void main() {
List<String> list = ["aa", "bb", "cc"];
String str =
"INSERT INTO (" + list.map((e) => '"$e"').join(', ') + ") VALUES (...) ";
print(str); // INSERT INTO ("aa", "bb", "cc") VALUES (...)
}
Can also be done like this which are a little shorter:
void main() {
List<String> list = ["aa", "bb", "cc"];
String str =
"INSERT INTO (${list.map((e) => '"$e"').join(', ')}) VALUES (...) ";
print(str); // INSERT INTO ("aa", "bb", "cc") VALUES (...)
}
I should add that this is just a solution for solving your specific problem. For real SQL escaping from unknown input, you should take into account that the String can contain " and need to escape it correctly.

how to insert new row by using executeUpdate - FMdatabase

I'm using fmdb library.
https://github.com/ccgus/fmdb
I would like to insert new records but I don't know how can I do:
if let db = database, let q = db.executeUpdate("insert into \(table) (catid,subcat_id,id,subcat_title,title,description,lat,lon,takhfif,images,wifi,apple_health,wc,full_time,pos,work_hours,phone,mobile,fax,website,email,address,facebook,instagram,linkedin,telegram,googleplus,twitter,publish,feature,manager,city,rating_sum,rate_count,lastip,parking,isMallID,mallID,discount_images,price_images,newProduct_images,services_images,order_online,out_upon,cat_title,cat_icon,last_modify,item_logo,cat_logo,rate_sum1,rate_sum2,rate_sum3,rate_count1,rate_count2,rate_count3,rate_title1,rate_title2,rate_title3,rate_enable) values (\(catid),\(subcat_id),\(id),'\(subcat_title)','\(title)','\(description)','\(lat)','\(lon)','\(takhfif)','\(images)',\(wifi),\(apple_health),\(wc),\(full_time),\(pos),'\(work_hours)','\(phone)','\(mobile)','\(fax)','\(website)','\(email)','\(address)','\(facebook)','\(instagram)','\(linkedin)','\(telegram)','\(googleplus)','\(twitter)',\(publish),\(feature),'\(manager)','\(city)',\(rating_sum),\(rate_count),'\(lastip)',\(parking),\(isMallID),\(mallID),'\(discount_images)','\(price_images)','\(newProduct_images)','\(services_images)',\(order_online),\(out_upon),'\(cat_title)','\(cat_icon)',\(last_modify),'\(item_logo)','\(cat_logo)',\(rate_sum1),\(rate_sum2),\(rate_sum3),\(rate_count1),\(rate_count2),\(rate_count3),'\(rate_title1)','\(rate_title2)','\(rate_title3)',\(rate_enable))") {
}
but I got this message before building:
cannot invoke executeUpdate with an argument list of type string
You used the api wrong. The first parameter is the sql, each value has to be represented with an "?". The second one (values) is an array of the values. Try this:
if let db = database, let q = db.executeUpdate(
"insert into \(table) (catid,subcat_id,id,subcat_title,title,description,lat,lon,takhfif,images,wifi,apple_health,wc,full_time,pos,work_hours,phone,mobile,fax,website,email,address,facebook,instagram,linkedin,telegram,googleplus,twitter,publish,feature,manager,city,rating_sum,rate_count,lastip,parking,isMallID,mallID,discount_images,price_images,newProduct_images,services_images,order_online,out_upon,cat_title,cat_icon,last_modify,item_logo,cat_logo,rate_sum1,rate_sum2,rate_sum3,rate_count1,rate_count2,rate_count3,rate_title1,rate_title2,rate_title3,rate_enable) values (?, ?, ? ...., ?, ?, ? )"
values: [catid, subcat_id, id, subcat_title, title, description, lat, lon, takhfif, images, wifi, apple_health, wc, full_time, pos, work_hours, phone, mobile, fax, website, email, address, facebook, instagram, linkedin, telegram, googleplus, twitter, publish, feature, manager, city, rating_sum, rate_count, lastip, parking, isMallID, mallID, discount_images, price_images, newProduct_images, services_images, order_online, out_upon, cat_title, cat_icon, last_modify, item_logo, cat_logo, rate_sum1, rate_sum2, rate_sum3, rate_count1, rate_count2, rate_count3, rate_title1, rate_title2, rate_title3, rate_enable]) {
}
Note: I didn't count the values, so you have to ensure that the count of "?" inside of the string is exactly the count of your values!
Maybe you should think about a function to improve the readability of your code. Something like that:
extension FMDatabase {
func insert(into table: String, row values: [(column: String, content: Any)]) {
guard (values.count > 0) else { fatalError(values.count > 0, "Inserting nothing into a table makes no sense!") }
var sql = "INSERT INTO \(table) ("
var contents: [Any] = []
for (index, value) in values.enumerated() {
sql += index == 0 ? value.column : ", \(value.column)"
contents.append(value.content)
}
sql += ") VALUES ("
for index in 0..<values.count {
sql += index == 0 ? "?" : ", ?"
}
sql += ")"
executeUpdate(sql, values: contents)
}
}
Then you can insert values like that:
if let db = database {
db.insert(
into: table,
row: [
(column: "catid", content: catid),
(column: "subcat_id", content: subcat_id),
...
(column: "rate_enable", content: rate_enable)
]
)
}
Isn't it better readable?

How to process async code in order

I have read a lot of postings and articles on this (async and Futures), and I am unsure what is the current situation. I have attempted a number of different variations.
I am attempting the process the following code in sequence :
import 'dart:async' as async;
import 'dart:io';
import 'dart:math';
import 'package:postgresql/postgresql.dart' as pg;
var uri = 'postgres://admin:admin#localhost:5432/testdb';
List lgNames = ['Peter', 'Robert', 'Mary', 'Marg', 'William',
'Vern', 'Dermot', 'Monty', 'Peggy', 'Sue', 'William'];
List lgUsed = [];
Random rand1 = new Random();
void main() {
pg.connect(uri).then((oDb) {
print ("Connected to database");
fClearTable(oDb).then((String sResult){
print(sResult);
for (int iPos = 0; iPos < 3; iPos++) {
fListTable(oDb, "Select before Insert number ${iPos+1}").then((String sResult) =>
print(sResult));
fInsertData(oDb, "Insert Number ${iPos+1}").then((String sResult) =>
print(sResult));
fListTable(oDb, "Select after Insert number ${iPos+1}").then((String sResult) =>
print(sResult));
}
});
});
}
async.Future<String> fClearTable(oDb) {
async.Completer oCompleter = new async.Completer();
oDb.execute("DELETE FROM test01").then((oResult){
oCompleter.complete("Table has been cleared");
});
return oCompleter.future;
}
async.Future<List> fListTable(oDb, sMsg) {
async.Completer oCompleter = new async.Completer();
oDb.query("SELECT * FROM test01").toList().then((lResult){
String sResult = "$sMsg = $lResult";
oCompleter.complete(sResult);
});
return oCompleter.future;
}
async.Future<String> fInsertData(oDb, sMsg) {
async.Completer oCompleter = new async.Completer();
oDb.execute("Start Transaction").then((oResult){
String sName;
for (bool tFound = true; tFound;) {
int iPos = rand1.nextInt(10);
sName = lgNames[iPos];
tFound = false; // init
for (iPos = 0; iPos < lgUsed.length && !tFound; iPos++){
tFound = (sName == lgUsed[iPos]);
}
}
lgUsed.add(sName);
String sSql = """INSERT INTO test01 (name)
VALUES ('$sName')""";
print("$sSql");
oDb.execute(sSql).then((oVal){
oDb.execute("COMMIT").then((oVal){
oCompleter.complete(sMsg);
});
});
});
return oCompleter.future;
}
The intention of the program is to loop three times and :
a) Select the Table
b) Insert a row into the table.
c) Select the Table
The output from the program clearly shows all three Inserts simultaneously at the end.
The output from the program is as follows :
Connected to database
Table has been cleared
Select before Insert number 1 = []
INSERT INTO test01 (name)
VALUES ('Vern')
Select after Insert number 1 = []
Select before Insert number 2 = []
INSERT INTO test01 (name)
VALUES ('Peter')
Select after Insert number 2 = []
Select before Insert number 3 = []
INSERT INTO test01 (name)
VALUES ('Robert')
Select after Insert number 3 = []
Insert Number 1
Insert Number 2
Insert Number 3
A Select in psql on termination of the program shows :
testdb=# select * from test01;
id | name
-----+--------
157 | Vern
158 | Peter
159 | Robert
(3 rows)
Is there a way to achieve what I want IE. For the Selects after the Inserts show the new values in the table?
Any relevant comments are welcome.
To start, here's a great article about how Futures work in Dart.
What you really need to know is that the code inside Futures run after all synchronous code has completed. Further, Futures by definition complete asynchronously, so it is not reasonable to expect one Future to complete before another unless they are chained.
That is to say:
main() {
print('first');
someFutureCall().then((_) => print('second?'));
someOtherFutureCall().then((_) => print('third?'));
print('last?');
}
This will print: 'first' and then 'last?' and then the two Futures can complete in any order. You cannot tell which will complete first. All you know is that the synchronous code happens first.
To have the correct order, do:
main() {
print('first');
someFutureCall()
.then((_) => print('second?'))
.then((_) => someOtherFutureCall())
.then((_) => print('third?'))
.then((_) => print('last?'));
}
So your sample uses Future in a way that the order is not preserved. What you want is this:
void main() {
pg.connect(uri).then((oDb) {
print ("Connected to database");
return fClearTable(oDb);
}).then((String sResult){
print(sResult);
return Future.forEach([0, 1, 2], (i) {
return fListTable(oDb, "Select before Insert number ${iPos+1}")
.then((String sResult) => print(sResult))
.then((_) => fInsertData(oDb, "Insert Number ${iPos+1}"))
.then((String sResult) => print(sResult))
.then((_) => fListTable(oDb, "Select after Insert number ${iPos+1}"))
.then((String sResult) => print(sResult));
});
});
}
See also the API documentation for Futures.

Filtered tokenised search result with Linq and EF

I'm looking for a way to create a filtering system based upon tokenised query strings to return a list of farms.
The filtering mechanism would hopefully be flexible enough for me to supply the tokens in any order to return results.
The rules for search would be like this:
state:WA crop:Banana
would give me a filtered list of all farms in WA with the crop banana.
crop:Banana state:WA
should return the same result.
city:Albany crop:Banana
would give me a filtered list of all farms in Albany with the crop banana.
Each of the values supplied could be wrapped in quotation marks to allow space separated values to be grouped. e.g
city:"Mount barker" crop:Banana
would give me a filtered list of all farms in Mount Barker with the crop banana.
Furthermore any non tokenised queries would just look within a farms Details property to return the list of farms again with quotation marks combining multiple word queries.
---------------------------------------EDIT--------------------------------------------
My current search system using predicates is coded as follows. it's long (sorry) and is my first attempt though I'm hoping this could be refactored by some kind soul.
Many thanks in advance:
public ActionResult Search(string query, int? page)
{
IQueryable<Farm> farms = this.ReadOnlySession.All<Farm>();
if (!String.IsNullOrWhiteSpace(query))
{
// http://petemontgomery.wordpress.com/2011/02/10/a-universal-predicatebuilder
var predicate = PredicateBuilder.True<Farm>();
// We want to replace the spaces in quoted values here so we can split by space later.
// http://stackoverflow.com/questions/2148587/regex-quoted-string-with-escaped-quotes-in-c
Regex quoted = new Regex(#"""[^""\\]*(?:\\.[^""\\]*)*""");
foreach (var match in quoted.Matches(query))
{
query = query.Replace(match.ToString(), match.ToString().Replace(' ', '-'));
}
// Tidy up the query to remove "".
string[] splitQuery = HttpUtility.UrlDecode(query).Replace("\"", "").Split(' ');
Dictionary<string, string> tokenDictionary = new Dictionary<string, string>();
// Loop through our string[] and create a dictionary. Guids used to allow multiple keys
// of the same value.
Parallel.ForEach(splitQuery, subQuery =>
{
string[] tempArray = subQuery.Split(':');
if (tempArray.Length == 2)
{
tokenDictionary.Add(String.Format("{0}:{1}", tempArray[0], Guid.NewGuid()), tempArray[1]);
}
else
{
tokenDictionary.Add(String.Format("description:{0}", Guid.NewGuid()), subQuery);
}
});
// Loop through the dictionary and create our predicate.
foreach (KeyValuePair<string, string> item in tokenDictionary)
{
string value = item.Value.Replace('-', ' ');
string key = item.Key.Split(':')[0].ToUpperInvariant();
switch (key)
{
case "CROP":
value = Utilities.CreateSlug(value, OzFarmGuideConfig.RemoveDiacritics);
predicate = predicate.And(x => x.Crops.Any(y => value.Equals(y.Slug, StringComparison.OrdinalIgnoreCase)));
break;
case "STATE":
predicate = predicate.And(x => value.Equals(x.City.State.Name, StringComparison.OrdinalIgnoreCase));
break;
case "CITY":
value = Utilities.CreateSlug(value, OzFarmGuideConfig.RemoveDiacritics);
predicate = predicate.And(x => value.Equals(x.City.Slug, StringComparison.OrdinalIgnoreCase));
break;
default:
predicate = predicate.And(x => !String.IsNullOrWhiteSpace(x.Details) && x.Details.Contains(value));
break;
}
}
farms = farms.Where(predicate).OrderByDescending(x => x.Rating)
.ThenByDescending(x => x.RatingVotes);
PagedList<Farm> pagedFarms = new PagedList<Farm>(farms, page.HasValue ? page.Value - 1 : 0, 5);
return View(pagedFarms);
}
else
{
PagedList<Farm> pagedFarms = null;
return View(pagedFarms);
}
}
Just a guess, would the problem correct itself with the introduction of DefaultIfEmpty()?
default:
// This is not working at the mo. Getting a null exception when we try
// to initialise PagedList.
predicate = predicate.And(x => x.Details.DefaultIfEmpty().Contains(value));
break;

Resources