Result of svn merge -r69150:69158 svn+ssh://svn/svn/linden/branches/named-queries-py3 into release.
parent
0bd992b07c
commit
80dfa222fd
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
"""\
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue