Result of svn merge -r69150:69158 svn+ssh://svn/svn/linden/branches/named-queries-py3 into release.

meow-7.2.2
Aaron Brashears 2007-09-10 20:10:56 +00:00
parent 0bd992b07c
commit 80dfa222fd
10 changed files with 367 additions and 10 deletions

View File

@ -39,8 +39,8 @@ def update(xml_file):
config_file.close()
_g_config_dict.update(overrides)
def get(key):
def get(key, default = None):
global _g_config_dict
if _g_config_dict == None:
load()
return _g_config_dict.get(key)
return _g_config_dict.get(key, default)

View File

@ -89,9 +89,12 @@ class UUID(object):
def __eq__(self, other):
if isinstance(other, (str, unicode)):
other = UUID(other)
return other == str(self)
return self._bits == getattr(other, '_bits', '')
def __ne__(self, other):
return not self.__eq__(other)
def __le__(self, other):
return self._bits <= other._bits

View File

@ -14,6 +14,49 @@ from indra.ipc import saranwrap
import MySQLdb
# method 2: better -- admits the existence of the pool
# dbp = my_db_connector.get()
# dbh = dbp.get()
# dbc = dbh.cursor()
# dbc.execute(named_query)
# dbc.close()
# dbp.put(dbh)
class DatabaseConnector(object):
"""\
@brief This is an object which will maintain a collection of database
connection pools keyed on host,databasename"""
def __init__(self, credentials, min_size = 0, max_size = 4, *args, **kwargs):
"""\
@brief constructor
@param min_size the minimum size of a child pool.
@param max_size the maximum size of a child pool."""
self._min_size = min_size
self._max_size = max_size
self._args = args
self._kwargs = kwargs
self._credentials = credentials # this is a map of hostname to username/password
self._databases = {}
def credentials_for(self, host):
if host in self._credentials:
return self._credentials[host]
else:
return self._credentials.get('default', None)
def get(self, host, dbname):
key = (host, dbname)
if key not in self._databases:
new_kwargs = self._kwargs.copy()
new_kwargs['db'] = dbname
new_kwargs['host'] = host
new_kwargs.update(self.credentials_for(host))
dbpool = ConnectionPool(self._min_size, self._max_size, *self._args, **new_kwargs)
self._databases[key] = dbpool
return self._databases[key]
class ConnectionPool(Pool):
"""A pool which gives out saranwrapped MySQLdb connections from a pool
"""
@ -26,12 +69,14 @@ class ConnectionPool(Pool):
return saranwrap.wrap(MySQLdb).connect(*self._args, **self._kwargs)
def put(self, conn):
# poke the process to see if it's dead or None
# rollback any uncommitted changes, so that the next process
# has a clean slate. This also pokes the process to see if
# it's dead or None
try:
status = saranwrap.status(conn)
conn.rollback()
except (AttributeError, DeadProcess), e:
conn = self.create()
# TODO figure out if we're still connected to the database
if conn:
Pool.put(self, conn)
else:

View File

@ -61,7 +61,7 @@ def format(format_str, context):
end = format_str.index('}', pos)
#print "directive:", format_str[pos+1:pos+5]
if format_str[pos + 1] == '$':
value = context.get(format_str[pos + 2:end])
value = context[format_str[pos + 2:end]]
if value is not None:
value = format_value_for_path(value)
elif format_str[pos + 1] == '%':
@ -71,7 +71,7 @@ def format(format_str, context):
value = _fetch_url_directive(format_str[pos + 1:end])
else:
raise UnknownDirective, format_str[pos:end + 1]
if not value == None:
if value is not None:
format_str = format_str[:pos]+str(value)+format_str[end+1:]
substitutions += 1

View File

@ -17,6 +17,14 @@ try:
except:
pass
# convenience method for when you can't be arsed to make your own object (i.e. all the time)
_g_builder = None
def build(name, context):
global _g_builder
if _g_builder is None:
_g_builder = ServiceBuilder()
_g_builder.buildServiceURL(name, context)
class ServiceBuilder(object):
def __init__(self, services_definition = services_config):
"""\

View File

@ -0,0 +1,191 @@
"""\
@file named_query.py
@author Ryan Williams, Phoenix
@date 2007-07-31
@brief An API for running named queries.
Copyright (c) 2007, Linden Research, Inc.
$License$
"""
import MySQLdb
import os
import os.path
import time
from indra.base import llsd
from indra.base import config
from indra.ipc import russ
_g_named_manager = None
# this function is entirely intended for testing purposes,
# because it's tricky to control the config from inside a test
def _init_g_named_manager(sql_dir = None):
if sql_dir is None:
sql_dir = config.get('named-query-base-dir')
global _g_named_manager
_g_named_manager = NamedQueryManager(
os.path.abspath(os.path.realpath(sql_dir)))
def get(name):
"@brief get the named query object to be used to perform queries"
if _g_named_manager is None:
_init_g_named_manager()
return _g_named_manager.get(name)
def sql(name, params):
# use module-global NamedQuery object to perform default substitution
return get(name).sql(params)
def run(connection, name, params, expect_rows = None):
"""\
@brief given a connection, run a named query with the params
Note that this function will fetch ALL rows.
@param connection The connection to use
@param name The name of the query to run
@param params The parameters passed into the query
@param expect_rows The number of rows expected. Set to 1 if return_as_map is true. Raises ExpectationFailed if the number of returned rows doesn't exactly match. Kind of a hack.
@return Returns the result set as a list of dicts.
"""
return get(name).run(connection, params, expect_rows)
class ExpectationFailed(Exception):
def __init__(self, message):
self.message = message
class NamedQuery(object):
def __init__(self, filename):
self._stat_interval = 5000 # 5 seconds
self._location = filename
self.load_contents()
def get_modtime(self):
return os.path.getmtime(self._location)
def load_contents(self):
self._contents = llsd.parse(open(self._location).read())
self._ttl = int(self._contents.get('ttl', 0))
self._return_as_map = bool(self._contents.get('return_as_map', False))
self._legacy_dbname = self._contents.get('legacy_dbname', None)
self._legacy_query = self._contents.get('legacy_query', None)
self._options = self._contents.get('options', {})
self._base_query = self._contents['base_query']
self._last_mod_time = self.get_modtime()
self._last_check_time = time.time()
def ttl(self):
return self._ttl
def legacy_dbname(self):
return self._legacy_dbname
def legacy_query(self):
return self._legacy_query
def return_as_map(self):
return self._return_as_map
def run(self, connection, params, expect_rows = None, use_dictcursor = True):
"""\
@brief given a connection, run a named query with the params
Note that this function will fetch ALL rows. We do this because it
opens and closes the cursor to generate the values, and this isn't a generator so the
cursor has no life beyond the method call.
@param cursor The connection to use (this generates its own cursor for the query)
@param name The name of the query to run
@param params The parameters passed into the query
@param expect_rows The number of rows expected. Set to 1 if return_as_map is true. Raises ExpectationFailed if the number of returned rows doesn't exactly match. Kind of a hack.
@param use_dictcursor Set to false to use a normal cursor and manually convert the rows to dicts.
@return Returns the result set as a list of dicts, or, if the named query has return_as_map set to true, returns a single dict.
"""
if use_dictcursor:
cursor = connection.cursor(MySQLdb.cursors.DictCursor)
else:
cursor = connection.cursor()
statement = self.sql(params)
#print "SQL:", statement
rows = cursor.execute(statement)
# *NOTE: the expect_rows argument is a very cheesy way to get some
# validation on the result set. If you want to add more expectation
# logic, do something more object-oriented and flexible. Or use an ORM.
if(self._return_as_map):
expect_rows = 1
if expect_rows is not None and rows != expect_rows:
cursor.close()
raise ExpectationFailed("Statement expected %s rows, got %s. Sql: %s" % (
expect_rows, rows, statement))
# convert to dicts manually if we're not using a dictcursor
if use_dictcursor:
result_set = cursor.fetchall()
else:
if cursor.description is None:
# an insert or something
x = cursor.fetchall()
cursor.close()
return x
names = [x[0] for x in cursor.description]
result_set = []
for row in cursor.fetchall():
converted_row = {}
for idx, col_name in enumerate(names):
converted_row[col_name] = row[idx]
result_set.append(converted_row)
cursor.close()
if self._return_as_map:
return result_set[0]
return result_set
def sql(self, params):
self.refresh()
# build the query from the options available and the params
base_query = []
base_query.append(self._base_query)
for opt, extra_where in self._options.items():
if opt in params and (params[opt] == 0 or params[opt]):
if type(extra_where) in (dict, list, tuple):
base_query.append(extra_where[params[opt]])
else:
base_query.append(extra_where)
full_query = '\n'.join(base_query)
# do substitution
sql = russ.format(full_query, params)
return sql
def refresh(self):
# only stat the file every so often
now = time.time()
if(now - self._last_check_time > self._stat_interval):
self._last_check_time = now
modtime = self.get_modtime()
if(modtime > self._last_mod_time):
self.load_contents()
class NamedQueryManager(object):
def __init__(self, named_queries_dir):
self._dir = os.path.abspath(os.path.realpath(named_queries_dir))
self._cached_queries = {}
def sql(self, name, params):
nq = self.get(name)
return nq.sql(params)
def get(self, name):
# new up/refresh a NamedQuery based on the name
nq = self._cached_queries.get(name)
if nq is None:
nq = NamedQuery(os.path.join(self._dir, name))
self._cached_queries[name] = nq
return nq

View File

@ -10,6 +10,8 @@
#include "linden_common.h"
#include "lltransactionflags.h"
#include "lltransactiontypes.h"
#include "lluuid.h"
const U8 TRANSACTION_FLAGS_NONE = 0;
const U8 TRANSACTION_FLAG_SOURCE_GROUP = 1;
@ -41,3 +43,85 @@ BOOL is_tf_owner_group(TransactionFlags flags)
return ((flags & TRANSACTION_FLAG_OWNER_GROUP) == TRANSACTION_FLAG_OWNER_GROUP);
}
void append_reason(
std::ostream& ostr,
S32 transaction_type,
const char* description)
{
switch( transaction_type )
{
case TRANS_OBJECT_SALE:
ostr << " for " << (description ? description : "<unknown>");
break;
case TRANS_LAND_SALE:
ostr << " for a parcel of land";
break;
case TRANS_LAND_PASS_SALE:
ostr << " for a land access pass";
break;
case TRANS_GROUP_LAND_DEED:
ostr << " for deeding land";
default:
break;
}
}
std::string build_transfer_message_to_source(
S32 amount,
const LLUUID& source_id,
const LLUUID& dest_id,
const std::string& dest_name,
S32 transaction_type,
const char* description)
{
lldebugs << "build_transfer_message_to_source: " << amount << " "
<< source_id << " " << dest_id << " " << dest_name << " "
<< (description?description:"(no desc)") << llendl;
if((0 == amount) || source_id.isNull()) return ll_safe_string(description);
std::ostringstream ostr;
if(dest_id.isNull())
{
ostr << "You paid L$" << amount;
switch(transaction_type)
{
case TRANS_GROUP_CREATE:
ostr << " to create a group";
break;
case TRANS_GROUP_JOIN:
ostr << " to join a group";
break;
case TRANS_UPLOAD_CHARGE:
ostr << " to upload";
break;
default:
break;
}
}
else
{
ostr << "You paid " << dest_name << " L$" << amount;
append_reason(ostr, transaction_type, description);
}
ostr << ".";
return ostr.str();
}
std::string build_transfer_message_to_destination(
S32 amount,
const LLUUID& dest_id,
const LLUUID& source_id,
const std::string& source_name,
S32 transaction_type,
const char* description)
{
lldebugs << "build_transfer_message_to_dest: " << amount << " "
<< dest_id << " " << source_id << " " << source_name << " "
<< (description?description:"(no desc)") << llendl;
if(0 == amount) return std::string();
if(dest_id.isNull()) return ll_safe_string(description);
std::ostringstream ostr;
ostr << source_name << " paid you L$" << amount;
append_reason(ostr, transaction_type, description);
ostr << ".";
return ostr.str();
}

View File

@ -24,4 +24,22 @@ BOOL is_tf_source_group(TransactionFlags flags);
BOOL is_tf_dest_group(TransactionFlags flags);
BOOL is_tf_owner_group(TransactionFlags flags);
// stupid helper functions which should be replaced with some kind of
// internationalizeable message.
std::string build_transfer_message_to_source(
S32 amount,
const LLUUID& source_id,
const LLUUID& dest_id,
const std::string& dest_name,
S32 transaction_type,
const char* description);
std::string build_transfer_message_to_destination(
S32 amount,
const LLUUID& dest_id,
const LLUUID& source_id,
const std::string& source_name,
S32 transaction_type,
const char* description);
#endif // LL_LLTRANSACTIONFLAGS_H

View File

@ -54,7 +54,7 @@ void LLHTTPClient::Responder::completedRaw(U32 status, const std::string& reason
LLBufferStream istr(channels, buffer.get());
LLSD content;
if (200 <= status && status < 300)
if (isGoodStatus(status))
{
LLSDSerialize::fromXML(content, istr);
/*
@ -73,7 +73,7 @@ void LLHTTPClient::Responder::completedRaw(U32 status, const std::string& reason
// virtual
void LLHTTPClient::Responder::completed(U32 status, const std::string& reason, const LLSD& content)
{
if (200 <= status && status < 300)
if(isGoodStatus(status))
{
result(content);
}

View File

@ -37,6 +37,14 @@ public:
Responder();
virtual ~Responder();
/**
* @brief return true if the status code indicates success.
*/
static bool isGoodStatus(U32 status)
{
return((200 <= status) && (status < 300));
}
virtual void error(U32 status, const std::string& reason); // called with bad status codes
virtual void result(const LLSD& content);