def executesql(self, query, placeholders=None, as_dict=False,fields=None, colnames=None):
....
if as_dict:
if not hasattr(adapter.cursor,'description'):
raise RuntimeError("database does not support executesql(...,as_dict=True)")
columns = adapter.cursor.description
fields = [f[0] for f in columns]
data = adapter._fetchall()
return [dict(zip(fields,row)) for row in data] def executesql(self, query, placeholders=None, as_dict=False,fields=None, colnames=None):
....
if as_dict:
if not hasattr(adapter.cursor,'description'):
raise RuntimeError("database does not support executesql(...,as_dict=True)")
columns = adapter.cursor.description
fields = colnames or [f[0] for f in columns]
data = adapter._fetchall()
return [dict(zip(fields,row)) for row in data] def select(self, *fields, **attributes):
adapter = self.db._adapter
tablenames = adapter.tables(self.query,
attributes.get('join',None),
attributes.get('left',None),
attributes.get('orderby',None),
attributes.get('groupby',None))
fields = adapter.expand_all(fields, tablenames)
return adapter.select(self.query,fields,attributes)
###############################################################
def quickSelect(self, *fields, **attributes):
query = self._select(*fields, **attributes)
colnames = query.split(' FROM')[0][7:-1].split(', ')
return self._db.executesql(query, colnames=colnames, as_dict=True)
###############################################################
def nested_select(self,*fields,**attributes):
return Expression(self.db,self._select(*fields,**attributes))class SimpleObject:
def __init__(self, attributes=None):
self.__dict__ = attributes
class Set(object):
...
def select(self, *fields, **attributes):
query = self._select(*fields, **attributes)
colnames = query.split(' FROM')[0][7:-1].split(', ')
tableNames = [colname.split('.')[0] for colname in colnames]
uniqueTableNames = set(tableNames)
records = self._db.executesql(query, colnames=colnames, as_dict=True)
if len(uniqueTableNames) is 1: # Single Table
tableNameLemgth = len(uniqueTableNames[0]) + 1
return [
SimpleObject(
dict(
[
(
key[tableNameLemgth:-1],
value
) for key, value in record.iteritems()
]
) for record in records
)
]
else: # Multiple Tables (JOIN)
tableNamesLemgth = dict( # A dictionary mapping table-names to their length+1
[ # This is for later "easy" splicing of the key-names
(
tableName,
(len(tableName) + 1)
) for tableName in uniqueTableNames
]
)
return [
SimpleObject(
dict(
[
(
tableName,
SimpleObject(
dict(
[
(
key[tableNamesLemgth[tableName]:-1],
value
) for key, value in record.iteritems() if (
key[:(tableNamesLemgth[tableName] - 1)] == tableName
)
]
)
)
) for tableName in tableNames
]
)
) for record in records
]db(query).select(..., processor=myprocessor)db.executesql(db(query)._select(), as_dict=True)10x Niphlod, I'll check that tomorrow...I can provide any code you like - this is solid and consistent.I am running this on my desktop-workstation which runs windows7x64.It's an Intel Core-i7 870 w/ 8GB RAM, running Python 2.6x64 via a simple command-line g-event server...Don't think I have a problem there...The database itself sits as a stand-alone service on an old "boxx" pizza-box-like server on our server-room, running nothing but PostgreSQL 9.3 (x64) and PG-Bouncer (for connection pooling) - on a minimalist CentOS 6.4 (x64) installation.I optimized it as far as I can, with the data weighing less than a 100MB total, I have the data-folder sitting on a mounted "tmpfs" of 3GB (a-la RAMDISK), and the schema is heavily "index"ed,, so the queries are as fast as they can be.As I said, it may be a case of simply having a very fast database-story...
"I'd guess your usecase is either 1k rows with 50"
That's exactly what I said - yes, it's a ~1K rows query, of a JOIN of 2 tables, selecting for about 20 columns.That is our use-case - it can't be any different (at least not easily...)We are not really using this version of web2py - I just did that for texting and posting here.But we initially encountered this case using a very old version of web2py (1.8.95) - in which the exact numbers where:904 rows in the result-set:- Regular select() : 2.241 seconds- In it, the portion that the executesql took: 0.023 seconds.That is roughly a x44 times gap...
And as I said - Trying to convert the result-set into a simple list of simple-class-instances, yielded similar results.Which made it conclusive : The overhead was in the object-instanciation and other pluming-related stuff like "__getitem__" and ".iteritems()" and the like...I think the test-code you gave here is interesting - but to make it closer to our use-case, I would have a more complex query tried.First, have 2 tables with a foreign-key linking them.Second, they should each have about ~20+ fields of varying types.Then the query should be a join of them, getting most/all columns.
What I notices, was that a "flat" query (one with no JOINs) may end-up being MUCH heavier to parse into Rows (or Rows-like) objects, as there are nested-loops involved, for having these intermediary Table-representing objects, that then have to have the result-set filtered-into them, according to each colname's "table-name" portion.
--
Resources:
- http://web2py.com
- http://web2py.com/book (Documentation)
- http://github.com/web2py/web2py (Source code)
- https://code.google.com/p/web2py/issues/list (Report Issues)
---
You received this message because you are subscribed to the Google Groups "web2py-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to web2py+un...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
You can also always do:db.executesql(db(query)._select(), as_dict=True)
to get just a list of dictionaries for any DAL select. Allowing custom column names, as you suggest, sounds like a good idea.
Use the profiler to make your tests even more effective
C:\Python26\python.exe F:/Experimental/web2py_272/web2py/test_executesql.py
Times are for 100 iterations
Preparing....
started bench
speed_executesql() total=1.132703 avg=0.011327
speed_normal_rows() total=5.320367 avg=0.053204
speed_rows_with_cacheable() total=5.349665 avg=0.053497
speed_executesql_with_colnames() total=1.083485 avg=0.010835
speed_rows_with_simple_processor() total=1.202628 avg=0.012026
Process finished with exit code 0
Times are for 10 iterations
Preparing....
started bench
speed_normal_rows() total=47.711369 avg=4.771137
speed_executesql_with_colnames() total=0.513249 avg=0.051325
10x Niphlod, I'll check that tomorrow...I can provide any code you like - this is solid and consistent.
The interesting (and somewhat disappointing) factoid I got from your results, was that PyPy was just as "slow" in un-parsed mode... ;)
C:\Python26\python.exe F:/Experimental/web2py_272/web2py/test_executesql.py
Times are for 100 iterations
Preparing....
started bench
speed_executesql() total=0.920402 avg=0.009204
speed_normal_rows() total=20.190159 avg=0.201902
speed_rows_with_cacheable() total=18.954632 avg=0.189546
speed_executesql_with_colnames() total=1.070893 avg=0.010709
speed_rows_with_simple_processor() total=1.118015 avg=0.011180
Process finished with exit code 0
Times are for 100 iterationsPreparing....started benchspeed_executesql() total=0.876057 avg=0.008761speed_normal_rows() total=15.076992 avg=0.150770speed_rows_with_cacheable() total=13.477355 avg=0.134774speed_executesql_with_colnames() total=0.989993 avg=0.009900speed_rows_with_simple_processor() total=1.084824 avg=0.010848
Times are for 100 iterations, of querying for 10000 results, each with 21 values
Preparing....
started bench
speed_executesql() total=11.081626 avg=0.110816
speed_normal_rows() total=204.889276 avg=2.048893
speed_rows_with_cacheable() total=190.929696 avg=1.909297
speed_executesql_with_colnames() total=13.822615 avg=0.138226
speed_rows_with_simple_processor() total=13.638187 avg=0.136382
Times are for 100 iterations, of querying for 1000 results, each with 201 values
Preparing....
started bench
speed_executesql() total=10.222960 avg=0.102230
speed_normal_rows() total=159.059873 avg=1.590599
speed_rows_with_cacheable() total=157.991245 avg=1.579912
speed_executesql_with_colnames() total=10.322220 avg=0.103222
speed_rows_with_simple_processor() total=10.510867 avg=0.105109C:\Python26\python.exe F:/Experimental/web2py_272/web2py/test_executesql_0.3.py
Preparing....
Times are for 100 iterations, into 50 tables, of querying for 1 results, each with 99 values
started bench
speed_executesql() total=0.019787 avg=0.000198
speed_normal_rows() total=0.731611 avg=0.007316
speed_rows_with_cacheable() total=0.669495 avg=0.006695
speed_executesql_with_colnames() total=0.016620 avg=0.000166
speed_rows_with_simple_processor() total=0.364101 avg=0.003641
Process finished with exit code 0
C:\Python26\python.exe F:/Experimental/web2py_272/web2py/test_executesql_0.3.py
Preparing....
Times are for 100 iterations, into 5 tables, of querying for 1000 results, each with 54 values
started bench
speed_executesql() total=2.619404 avg=0.026194
speed_normal_rows() total=52.479113 avg=0.524791
speed_rows_with_cacheable() total=48.437764 avg=0.484378
speed_executesql_with_colnames() total=2.789842 avg=0.027898
speed_rows_with_simple_processor() total=2.865223 avg=0.028652C:\Python26\python.exe F:/Experimental/web2py_272/web2py/test_executesql_0.3.pyPreparing....Times are for 100 iterations, into 2 tables, of querying for 1 results, each with 3 valuesstarted benchspeed_executesql() total=0.002431 avg=0.000024speed_normal_rows() total=0.028979 avg=0.000290speed_rows_with_cacheable() total=0.029703 avg=0.000297speed_executesql_with_colnames() total=0.002551 avg=0.000026speed_rows_with_simple_processor() total=0.015669 avg=0.000157
Process finished with exit code 0import timeit
from gluon.dal import DAL, Field
tableCount = 2
fieldCount = 1
recordCount = 1
times = 100
##prepare a large dict
...selection = [db[tableName].ALL for tableName in tableNames]
sql = db(query)._select(*selection)
sql_colnames = sql.split(' FROM')[0][7:].split(', ')
But this is using just simple string-based fields... It goes way-up from there when you introduce other data-types.
fields[j]['type']
amount = 100
def doWorkDirectly():
List = []
for i in range(amount):
List.appent('a')
def doWork(List): List.append('a')
def doWorkIndirectly():
List = []
for i in range(amount):
doWork(List)fields = colnames or [f[0] for f in columns]
sql = db(db.coordinate.is_active == True)._select(db.coordinate.point2d.count(), db.coordinate.point3d.count())rows = db.executesql(sql, colnames=['count_point2d', 'count_point3d'], as_dict=True)print sql, rows[0:1]SELECT COUNT(coordinate.point2d), COUNT(coordinate.point3d) FROM coordinate WHERE (coordinate.is_active = 'T');[{'count_point2d': 35148L, 'count_point3d': 35148L}] sql = db(db.coordinate.is_active == True)._select(db.coordinate.point2d.count(), db.coordinate.point3d.count()) rows = db.executesql(sql, as_dict=True) print sql, rows[0:1] SELECT COUNT(coordinate.point2d), COUNT(coordinate.point3d) FROM coordinate WHERE (coordinate.is_active = 'T'); [{'count': 35148L}]count2d = db.coordinate.point2d.count().with_alias('count2d')count3d = db.coordinate.point2d.count().with_alias('count3d')sql = db(db.coordinate)._select(count2d, count3d)print sqlprint db.executesql(sql,as_dict=True)SELECT COUNT(coordinate.point2d) AS count2d, COUNT(coordinate.point2d) AS count3d FROM coordinate WHERE (coordinate.id IS NOT NULL);[{'count2d': 60480L, 'count3d': 60480L}]--
Resources:
- http://web2py.com
- http://web2py.com/book (Documentation)
- http://github.com/web2py/web2py (Source code)
- https://code.google.com/p/web2py/issues/list (Report Issues)
---
You received this message because you are subscribed to a topic in the Google Groups "web2py-users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/web2py/6c0iPBa4xH4/unsubscribe.
To unsubscribe from this group and all its topics, send an email to web2py+un...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Thanks both. It is great to see the improvements in Web2py.