Good afternoon,
I am using Clojure 1.8, PostgreSQL 9.4 and YeSQL 0.5.2
I have an web app that has a page to insert multiple prices. I get the prices from a HTML Form using Post method. It comes as a map of vector for me. I convert it to a vector of maps so it is easier to manipulate the data. Then I delete previous entries if exists and I do the treatments to the fields and call the DB transaction to insert into the database.
When I use lein ring server it works fine. But when I deploy to Tomcat 9 using lein ring uberwar, the app inserts only the last row.
Here are the my web page:
Each row in the table will be a SQL Insert
My connection:
(ns satus-cotacao.model.connection)
(def db-spec {:classname "org.postgresql.Driver"
:subprotocol "postgresql"
:subname "//localhost/database" ;; 192.168.0.x:port when using other database from the internal network.
:user "root"
:password "root"})
And my defqueries
(ns satus-cotacao.model.cotacao_model
(:require ...
[satus-cotacao.model.connection :refer [db-spec]]
...)
(defqueries "satus_cotacao/model/cotacao.sql"
{:connection db-spec})
The Post request that calls the lcto-cotacao-page using the Form as parameter
POST "/cotacao/:cod" [cod & form :as session] (lcto-cotacao-page cod form session))
The lcto-cotacao-page function to call the Delete and the Insert functions.
(defn lcto-cotacao-page
[cod lcto session]
(try
(cotacao/deleta-lcto-cotacao! cod (:identity session))
(cotacao/insere-lctos-cotacoes! cod (:identity session) lcto)
(layout/render "home.html" {:session session})
(catch Exception e
(do
(timbre/error e)))))
The delete-lcto-cotacao! function and SQL query
(defn deleta-lcto-cotacao!
[codcotacao enti_codigo]
(jdbc/with-db-transaction [tx db-spec]
(delete-lcto-cotacao! {:codcotacao codcotacao
:enti_codigo enti_codigo}
{:connection tx})))
-- name: delete-lcto-cotacao!
DELETE FROM lctocotacao
WHERE lcco_codentidade = :enti_codigo
AND lcco_codcontrole = :codcotacao
The delete works fine, it deletes all entries.
The insere-lctos-cotacoes! to convert from map of vector to vector of maps and create a recursive call through this vector
(defn insere-lctos-cotacoes!
[cod enti lctos]
(if (vector? (:lcco_codprod lctos))
(let [lcto-vec (util/from-map-of-vector-to-vector-of-maps lctos)]
(loop [lcto lcto-vec]
(when (not-empty lcto)
(insere-lcto-cotacao<! cod enti (first lcto))
(recur (rest lcto)))))
(insere-lcto-cotacao<! cod enti lctos)))
Then the function insere lcto-cotacao<! that treats the fields
(defn insere-lcto-cotacao<!
[cod enti lcto]
(let [lcto_codentidade enti
lcto_codcontrole cod
lcto_dtmvto (util/convert-to-data-type (.format (java.text.SimpleDateFormat. "MM/dd/yyyy") (Date.)))
lcto_codprod (Integer. (:lcco_codprod lcto))
lcto_qemb (bigdec (:lcco_qemb lcto))
lcto_qtde (bigdec (:lcco_qtde lcto))
lcto_fpgto (util/empty-field? (:lcco_fpgto lcto) false)
lcto_preco (util/empty-field? (:lcco_preco lcto) true)
lcto_codempresa (util/empty-field? (:lcco_codempresa lcto) false)
new-lcto (assoc lcto :lcco_codentidade lcto_codentidade
:lcco_codcontrole lcto_codcontrole
:lcco_dtmvto lcto_dtmvto
:lcco_codprod lcto_codprod
:lcco_preco lcto_preco
:lcco_qemb lcto_qemb
:lcco_qtde lcto_qtde
:lcco_fpgto lcto_fpgto
:lcco_codempresa lcto_codempresa)]
(insere-lcto-transaction<! (dissoc new-lcto :tpemb :descricao :codbarras :marca))))
And finally the insere-lcto-transaction<! and the SQL query
(defn insere-lcto-transaction<!
[lcto]
(jdbc/with-db-transaction [tx db-spec]
(insert-lcto-cotacao<! lcto {:connection tx})))
-- name: insert-lcto-cotacao<!
INSERT INTO lctocotacao (lcco_codentidade, lcco_codprod, lcco_preco, lcco_dtmvto, lcco_codempresa, lcco_fpgto, lcco_qemb, lcco_qtde, lcco_codcontrole)
VALUES(:lcco_codentidade, :lcco_codprod, :lcco_preco, :lcco_dtmvto, :lcco_codempresa, :lcco_fpgto, :lcco_qemb, :lcco_qtde, :lcco_codcontrole);
Well, thats it. What is strange is that it works fine when I do lein ring server, but when I deploy to lein ring uberwar it doens't work properly.
Also, I tried using Tomcat in Windows and Debian.
Does anyone have an idea why is it happening?
EDIT
After logging, I got some results:
When running local lein ring server
What the POST return into the var form is the same content as the key :param in my session
Also in session, I have the :form-paramsthat has the same content, but instead of using keys like :lcco_fpgto, it uses strings like '"lcco_fpgto"'
{:lcco_fpgto ["001" "001" "001" "001"],
:tpemb ["CX" "CX" "PT" "CX"],
:lcco_qtde ["150.00000" "1.00000" "30.00000" "1.00000"],
:descricao ["Cerveja 350ml Skola" "Adoc 100ml Assugrin Diet" "Cafe 500g Corol Torrado Moido" "Deterg Liq 500ml Alpes Neutro"],
:lcco_codprod ["19" "28" "64" "123"],
:lcco_preco ["1.00000" "2.00000" "3.00000" "4.00000"],
:codbarras ["7896232111149" "7896060010492" "7896184000010" "7896274800124"],
:lcco_codempresa ["001" "001" "001" "001"],
:lcco_qemb ["12.00000" "12.00000" "10.00000" "24.00000"],
:marca ["Skol" "Assugrin" "Corol" "Alpes"],
:__anti-forgery-token ["Unbound: #'ring.middleware.anti-forgery/*anti-forgery-token*" "Unbound: #'ring.middleware.anti-forgery/*anti-forgery-token*" "Unbound: #'ring.middleware.anti-forgery/*anti-forgery-token*" "Unbound: #'ring.middleware.anti-forgery/*anti-forgery-token*"]}
When running the deployed app:
The POST return is the same as the :param key inside session (Only the last element)
{:lcco_fpgto "001",
:tpemb "CX",
:lcco_qtde "1.00000",
:descricao "Deterg Liq 500ml Alpes Neutro",
:lcco_codprod "123",
:lcco_preco "4",
:codbarras "7896274800124",
:lcco_codempresa "001",
:lcco_qemb "24.00000",
:marca "Alpes",
:__anti-forgery-token "Unbound: #'ring.middleware.anti-forgery/*anti-forgery-token*"}
And what I found, is that the :form-params inside my session contains everything, but using strings instead of keys.
:form-params {
"lcco_preco" ["1" "2" "3" "4"],
"tpemb" ["CX" "CX" "PT" "CX"],
"descricao" ["Cerveja 350ml Skola" "Adoc 100ml Assugrin Diet" "Cafe 500g Corol Torrado Moido" "Deterg Liq 500ml Alpes Neutro"],
"lcco_codprod" ["19" "28" "64" "123"],
"lcco_fpgto" ["001" "001" "001" "001"],
"codbarras" ["7896232111149" "7896060010492" "7896184000010" "7896274800124"],
"marca" ["Skol" "Assugrin" "Corol" "Alpes"],
"lcco_qtde" ["150.00000" "1.00000" "30.00000" "1.00000"],
"__anti-forgery-token" ["Unbound: #'ring.middleware.anti-forgery/*anti-forgery-token*" "Unbound: #'ring.middleware.anti-forgery/*anti-forgery-token*" "Unbound: #'ring.middleware.anti-forgery/*anti-forgery-token*" "Unbound: #'ring.middleware.anti-forgery/*anti-forgery-token*"],
"lcco_qemb" ["12.00000" "12.00000" "10.00000" "24.00000"],
"lcco_codempresa" ["001" "001" "001" "001"]}
So, why is that happening? Is it wrong to use the var form that I get in here?
POST "/cotacao/:cod" [cod & form :as session] (lcto-cotacao-page cod form session))
Why is this difference happening?
EDIT 2
My middleware:
https://codeshare.io/mR7Zh
Related
I got this problem. Why i get this access error and how can i fix it?
Odoo Server Error - Access Error
Sorry, you are not allowed to access
this document. Only users with the following access level are
currently allowed to do that:
Administration/Settings
(Document model: ir.config_parameter) - (Operation: read, User: 21)
Here is my code:
Button submit:
<button string="Confirm" name="button_submit" states="draft" type="object" class="oe_highlight"/>
My python code:
def send_email(self, subject, message_body, email_from, email_to):
template_obj = self.env['mail.mail']
template_data = {
'subject': subject,
'body_html': message_body,
'email_from': email_from,
'email_to': email_to
}
template_id = template_obj.create(template_data)
template_obj.send(template_id)
template_id.send()
#api.multi
def request_recuitment_send_mail(self):
""" Send mail with wizard """
base_url = request.env['ir.config_parameter'].get_param('web.base.url')
base_url += '/web#id=%d&view_type=form&model=%s' % (self.id, self._name)
subject = '''Request recuitment for {}'''.format(self.job_id.name)
message_body = '''
<div style="font-size: medium;">
Dear {},
Please check this link for more information Click here
'''.format(
self.user_id.name,
base_url,
)
email_from = '''HR Recruiment <{}>'''.format(self.approver_id.work_email)
email_to = self.user_id.email
self.send_email(subject, message_body, email_from, email_to)
#api.multi
def button_approve(self):
subject = "Request recruitment for {self.job_id.name} has been approved "
body = '''
Position Request: {}
Quantity of Position: {}
Department: {}
Expected Gross Salary: {}
'''.format(
self.job_id.name,
self.quantity,
self.department_id.name,
self.salary_cross_expected
)
self.env['mail.message'].create({'message_type': "notification",
"subtype": self.env.ref("mail.mt_comment").id,
'body': body,
'subject': subject,
'needaction_partner_ids': [(4, self.user_id.partner_id.id,)],
'model': self._name,
'res_id': self.id,
})
self.request_recuitment_approved_send_mail()
self.write({'state': 'approved'})
It should be safe to use sudo() in this case:
request.env['ir.config_parameter'].sudo().get_param('web.base.url')
"Normal" Users don't have any rights on model ir.config_parameter (System parameters). Only the admin (one of its default access groups) or the superuser can read such parameters.
About sudo([flag=True]) from the current documentation (Odoo 15):
Returns a new version of this recordset with superuser mode enabled or disabled, depending on flag. The superuser mode does not change the current user, and simply bypasses access rights checks.
IMPORTANT: I'm not completely sure when it was changed, but IIRC the "current user change" was removed since Odoo 13. So for Odoo 12 sudo will change the current user, which for example will have impacts on default values on creation, created message authors, and so on.
In your case that's irrelevant, because you're only getting the base url or the parameter value, and that's it.
On Prestashop 1.7, how can I increase the maximum file name size of attachments?
While naming attachment files in the Back Office, I encounter the following error message.
How can I go around this problem in Prestashop 1.7? I looked up online and found some solutions for Prestashop 1.5 or 1.6, but not for 1.7.
Here are some links that I followed:
https://www.prestashop.com/forums/topic/63332-bug-the-field-mime-is-too-long-32-chars-max/?tab=comments#comment-1189602
https://www.prestashop.com/forums/topic/81129-solvedhow-to-change-the-length-of-the-file-name-of-the-attachement/?tab=comments#comment-1197114
Following the above links, I have tried:
In the DB, changed the 'name' column's type in the 'ps_attachment_lang' table to 'varchar(128)'.
In Attachment.php (/classes/Attachment.php), made sure the size is 128.
'name' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'required' => true, 'size' => 128],
In AdminProductsController.php (/controllers/admin/AdminProductsController.php), changed a part of function ajaxProcessAddAttachment(), from
if (!Validate::isGenericName($name)) {
$_FILES['attachment_file']['error'][] = $this->trans('Invalid name for %s language', [$language['name']], 'Admin.Notifications.Error');
} elseif (Tools::strlen($name) > 32) {
$_FILES['attachment_file']['error'][] = $this->trans('The name for %1s language is too long (%2d chars max).', [$language['name'], 32], 'Admin.Notifications.Error');
}
to
if (!Validate::isGenericName($name)) {
$_FILES['attachment_file']['error'][] = $this->trans('Invalid name for %s language', [$language['name']], 'Admin.Notifications.Error');
} elseif (Tools::strlen($name) > 64) {
$_FILES['attachment_file']['error'][] = $this->trans('The name for %1s language is too long (%2d chars max).', [$language['name'], 64], 'Admin.Notifications.Error');
}
But unfortunately, none of them worked.
Thanks in advance.
This is my own question but I found one solution.
In the AttachmentConstraint.php (in /src/Core/Domain/Attachment/Configuration/AttachmentConstraint.php), find the line
const MAX_NAME_LENGTH = 32;
and change it to whatever number that is lower than your database table can hold:
//const MAX_NAME_LENGTH = 32;
const MAX_NAME_LENGTH = 128; // this number needs to be lower than `name` column in `ps_attachment_lang` table
This should do the trick, but this solution requires changes in the core file. If there's anyone who knows a way we can achieve this without modifying the core files, please share that.
I'm working with Ecto (with Postgres) for the first time and I have the following two schemas (both somewhat simplified):
defmodule RailroadServer.Database.RailroadSystem do
#moduledoc """
Schema for an entire railroad system.
"""
use Ecto.Schema
import Ecto.Changeset
alias RailroadServer.Database
schema "railroad_systems" do
field :railroad_system_name, :string
has_many :depos, Database.Depo
end
#fields ~w(railroad_system_name)a
def changeset(data, params \\ %{}) do
data
|> cast(params, #fields)
|> validate_required([:railroad_system_name])
|> validate_length(:railroad_system_name, max: 50)
end
end
defmodule RailroadServer.Database.Depo do
#moduledoc """
Schema for a node that stores trains.
"""
use Ecto.Schema
import Ecto.Changeset
alias RailroadServer.Database
schema "depos" do
field :capacity, :integer
field :depo_uuid, :string
field :depo_name, :string
belongs_to :railroad_system, Database.RailroadSystem
end
#fields ~w(capacity depo_uuid depo_name)a
def changeset(data, params \\ %{}) do
data
|> cast(params, #fields)
|> validate_required([:capacity, :depo_uuid, :depo_name])
|> validate_number(:capacity, greater_than: 0)
|> validate_length(:depo_name, max: 50)
|> validate_length(:depo_uuid, max: 50)
|> foreign_key_constraint(:railroad_system_id)
end
end
Based on these migrations:
defmodule RailroadServer.Database.Repo.Migrations.CreateRailroadSystems do
use Ecto.Migration
def change do
create table(:railroad_systems) do
add :railroad_system_name, :varchar, null: false, size: 50
end
create unique_index("railroad_systems", [:railroad_system_name])
end
end
defmodule RailroadServer.Database.Repo.Migrations.CreateDepos do
use Ecto.Migration
def change do
create table(:depos) do
add :railroad_system_id, references("railroad_systems"), null: false
add :depo_uuid, :varchar, size: 50, null: false
add :depo_name, :varchar, size: 50, null: false
add :capacity, :integer, null: false
end
create index("depos", [:railroad_system_id])
create index("depos", [:depo_uuid], unique: true)
create index("depos", [:depo_name], unique: true)
end
end
Which I'm constructing with the following code:
def insert_railway_system(system_name, depos) do
cs = %RailroadSystem{}
|> RailroadSystem.changeset(%{railroad_system_name: system_name})
|> put_assoc(:depos, create_depos(depos))
if cs.valid? do
Repo.insert(cs)
else
{:error, cs}
end
end
_ = """
Uses a list of depo nodes to construct a list of depo changeset.
"""
defp create_depos(depos) do
Enum.map(depos, fn(depo) -> Depo.changeset(%Depo{}, depo) end)
end
However, when I run this function (with data that produces a valid changeset), I get a NULL column error because the foreign key for the railway system in the depo struct doesn't exist. How do I make sure that Ecto passes that foreign key?
The output:
19:06:07.401 [debug] QUERY OK db=0.8ms
begin []
19:06:07.406 [debug] QUERY OK db=0.6ms
INSERT INTO "railroad_systems" ("railroad_system_name") VALUES ($1) RETURNING "id" ["test Can insert railway system"]
19:06:07.409 [debug] QUERY ERROR db=2.7ms
INSERT INTO "depos" ("capacity","depo_name","depo_uuid") VALUES ($1,$2,$3) RETURNING "id" [23, "A depo", "d387a91b-db77-4758-87ed-9951d5c2de8a"]
19:06:07.410 [debug] QUERY OK db=0.1ms
rollback []
1) test Can insert railway system (RailroadServer.DatabaseTest)
apps/railroad_server/test/railroad_server/database_test.exs:9
** (Postgrex.Error) ERROR 23502 (not_null_violation) null value in column "railroad_system_id" violates not-null constraint
table: depos
column: railroad_system_id
Failing row contains (3, null, d387a91b-db77-4758-87ed-9951d5c2de8a, A depo, 23).
stacktrace:
(ecto_sql) lib/ecto/adapters/sql.ex:621: Ecto.Adapters.SQL.raise_sql_call_error/1
(ecto) lib/ecto/repo/schema.ex:649: Ecto.Repo.Schema.apply/4
(ecto) lib/ecto/repo/schema.ex:262: anonymous fn/15 in Ecto.Repo.Schema.do_insert/4
(ecto) lib/ecto/association.ex:927: Ecto.Association.BelongsTo.on_repo_change/5
(ecto) lib/ecto/association.ex:413: Ecto.Association.on_repo_change/7
(elixir) lib/enum.ex:1948: Enum."-reduce/3-lists^foldl/2-0-"/3
(ecto) lib/ecto/association.ex:392: Ecto.Association.on_repo_change/4
(ecto) lib/ecto/repo/schema.ex:811: Ecto.Repo.Schema.process_parents/4
(ecto) lib/ecto/repo/schema.ex:242: anonymous fn/15 in Ecto.Repo.Schema.do_insert/4
(ecto) lib/ecto/association.ex:662: Ecto.Association.Has.on_repo_change/5
(ecto) lib/ecto/association.ex:432: anonymous fn/8 in Ecto.Association.on_repo_change/7
(elixir) lib/enum.ex:1948: Enum."-reduce/3-lists^foldl/2-0-"/3
(ecto) lib/ecto/association.ex:428: Ecto.Association.on_repo_change/7
(elixir) lib/enum.ex:1948: Enum."-reduce/3-lists^foldl/2-0-"/3
(ecto) lib/ecto/association.ex:392: Ecto.Association.on_repo_change/4
(ecto) lib/ecto/repo/schema.ex:837: Ecto.Repo.Schema.process_children/5
(ecto) lib/ecto/repo/schema.ex:914: anonymous fn/3 in Ecto.Repo.Schema.wrap_in_transaction/6
(ecto_sql) lib/ecto/adapters/sql.ex:890: anonymous fn/3 in Ecto.Adapters.SQL.checkout_or_transaction/4
(db_connection) lib/db_connection.ex:1415: DBConnection.run_transaction/4
(railroad_server) lib/railroad_server/database.ex:61: RailroadServer.Database.insert_railway_system/4
Versions:
Elixir - 1.9.0
Ecto - 3.17
Postgrex - 0.14.3
Prosgres - 11.4
When I try an insert with identical code as in your insert_railway_system(), I do not get a NULL column error.
My schemas are similar. The only significant difference in my code is in the changeset where I have the constraint:
|> assoc_constraint()
instead of:
|> foreign_key_constraint()
But I changed my code to try the foreign_key_constraint(), leaving the argument the same, which is equivalent to your :railroad_system, and the insert still worked. The insert also worked when I did the equivalent of foreign_key_constraint(:railroad_system_id). In fact, if I use foreign_key_constraint(:hello_world), the insert still works, so as far as I can tell, the second argument to foreign_key_constraint() is ignored, which is puzzling. I even did mix ecto.reset, which deletes the repo/database, recreates the repo/database, then executes the migrations, which creates the tables in the repo/database, and I got the same results.
My "create_depos" migration has the equivalent of the following:
add :railroad_system_id, references(:railroad_systems)
Please post:
The create_depos() function (although for me just using an attributes map instead of a changeset also worked)
The full stack trace of the error.
Your migrations.
I want to add an item to my transaction.
$json = '
[
{
"name": "Voucher",
"description":"Voucher",
"price":"50.00",
"currency":"EUR",
"quantity":"1"
}
]';
$patchAddItem = new \PayPal\Api\Patch();
$patchAddItem->setOp('add')
->setPath('/transactions/0/item_list/items')
->setValue(json_decode($json));
$patchReplace = new \PayPal\Api\Patch();
$patchReplace->setOp('replace')
->setPath('/transactions/0/amount')
->setValue(json_decode('{
"total": "159.00",
"currency": "EUR",
}'));
$patchRequest = new \PayPal\Api\PatchRequest();
$patchRequest->setPatches(array($patchAddItem, $patchReplace));
try {
$this->payment->update($patchRequest, $this->apiContext);
} catch (PayPal\Exception\PayPalConnectionExceptio $ex) {
echo '<pre>';print_r(json_decode($ex->getData()));exit;
}
But I get following Error
Eception: Got Http response code 400 when accessing https://api.sandbox.paypal.com/v1/payments/payment/PAY... in PayPal-PHP-SDK/paypal/rest-api-sdk-php/lib/PayPal/Core/PayPalHttpConnection.php on line 154
PayPal-PHP-SDK/paypal/rest-api-sdk-php/lib/PayPal/Transport/PayPalRestCall.php on line 73: PayPal\Core\PayPalHttpConnection->execute("[{"op":"add","path":"/transactions/0/item_list/ite"... )
PayPal-PHP-SDK/paypal/rest-api-sdk-php/lib/PayPal/Common/PayPalResourceModel.php on line 102: PayPal\Transport\PayPalRestCall->execute(array[1],"/v1/payments/payment/PAY-1S151200BX2478240LEAG3CI","PATCH","[{"op":"add","path":"/transactions/0/item_list/ite"... ,null)
PayPal-PHP-SDK/paypal/rest-api-sdk-php/lib/PayPal/Api/Payment.php on line 615: PayPal\Common\PayPalResourceModel::executeCall("/v1/payments/payment/PAY-1S151200BX2478240LEAG3CI","PATCH","[{"op":"add","path":"/transactions/0/item_list/ite"... ,null,object,null)
At this moment I didn't execute the payment object. Do I have to edit the total attribut from amount too? Well, I tried this too, with same issue...
Even if you are sending only one item to PayPal you still have to set them as an item list with setItemList().
That array should be visible if you json_decode in your payment array:
[item_list] => Array
(
[items] => Array
(
[0] => Array
(
[name] => Ground Coffee 40 oz
[sku] => 123123
[price] => 52.80
[currency] => USD
[quantity] => 1
)
)
I had not run a patch for an item yet. I attempted to send an 'add' similar to your code and tried changing the path to '/transactions/0/item_list/items/1' using the next number in the items array. But could not get an add to work.
The only way I could modify the item_list was to do a complete 'replace' of the item_list, so in a running shopping cart would have to include all the items being purchased, not just the new item.
To do this I prefer to use the functions from the PayPal sdk vs building the json arrays. Their examples of how to create and execute a payment are fairly good and use the SDK functions. http://paypal.github.io/PayPal-PHP-SDK/sample/
However the example on updating a payments builds the json arrays outright.
Below is a testing function to modify the item_list using the Paypay PHP SDK Class Functions. I hard coded the Subtotal and Total to match the values coming form the shopping cart plus the increase from the new item. The item_list is also hard coded using PP's example data. Otherwise item's arrays would be built off of a user's shopping cart items. The type is set to 'replace'.
So, yes. Subtotals and Totals need to be updated to match as well, else the PP call will fail.
function updatePayPalPayment ($type, $createdPayment, $total, $subtotal, $shipping, $currency) {
$subtotal = '54.80';
$total = '71.73';
$details = new Details();
$details->setShipping($shipping)
->setSubtotal($subtotal);
$amount = new Amount();
$amount->setCurrency($currency)
->setTotal($total)
->setDetails($details);
$item1 = new Item();
$item1->setName('Ground Coffee 40 oz')
->setCurrency('USD')
->setQuantity(1)
->setSku("123123") // Similar to `item_number` in Classic API
->setPrice(52.80);
$item2 = new Item();
$item2->setName('Granola bars')
->setCurrency('USD')
->setQuantity(1)
->setSku("321321") // Similar to `item_number` in Classic API
->setPrice(2.0);
$itemList = new ItemList();
$itemList->setItems(array($item1, $item2));
$patchItem = new Patch();
$patchItem->setOp($type)
->setPath('/transactions/0/item_list')
->setValue($itemList);
$patchAmount = new Patch();
$patchAmount->setOp($type)
->setPath('/transactions/0/amount')
->setValue($amount);
$patchRequest = new PatchRequest();
$patchRequest->setPatches(array($patchAmount, $patchItem));
$update = $createdPayment->update($patchRequest, getApiContext());
return $update;
}
I also have found it very helpful to set the apiContext for logging to DEBUG and output to a file in development for much better error messages.
'log.LogEnabled' => true,
'log.FileName' => '_PayPal.log',
'log.LogLevel' => 'DEBUG',
Hope that helps.
I'd like to see if a migration of a dataset from PostgreSQL to Redis has a positive influence on a particular search query. Unfortunately, I don't really know how to organize the keys and values.
What I want is a that users are able to supply a list of properties and the application delivers a list of items in an ascending order of properties that have not been entered.
For example:
Item #1 { prop1, prop2, prop4, prop7 } Query: "prop1 prop3 prop4 prop5"
Item #2 { prop7, prop8 } Result: Item #3
Item #3 { prop2, prop3, prop5 } Item #1
Item #2
What I have come up with so far:
#!/usr/bin/python
properties = (1, 3, 4, 5)
items = ["Properties:%s:items" % id for property in properties ]
redis.zunionstore("query:related_items", items)
redis.zinterstore("query:result", { "Items:all": 1, "query:related_items": -1 })
This builds a sorted set of Items (all with a score of 1) that are connected with the user-entered Propertys. Afterwards, an intersection with the sorted set of all Items (where each value's score is the number of Propertys) is calculated. The weights are set to create a score of 0 if all Propertys of an Item are supplied in the query.
As the number of Items is about 600.000 entries this query takes approximately 4-6 seconds. Is there a better way to accomplish this?
I imagine you're looking for a Python solution, but the Ohm library for Ruby is my favorite of the Redis-based database analogues. Considering the similarities between Python and Ruby and Ohm's exceptional documentation, you might find some inspiration.
EDIT: Used real properties as stated in comments.
I think I did it (once more). I used PHPRedis.
I used sorted sets too, but I inverted your schema: Each zset represents an ingredient, and each recipe id is a member of that zset. So every zset has the same number of members, i.e., every recipe on the application. Every recipe uses or not a ingredient. That defines the score.
Loading is somewhat expensive, but query is done under 3s, for a sample with 12 ingredients and 600.000 recipes. (you've got a lot of them for sure!).
Loading
Pseudo-code:
For every ingredient i on the system
For every recipe j on the system
If recipe j uses the ingredient i Then
score = 1
INCR recipe:j:ing_count //Will help sorting
RPUSH recipe:j:ing_list i //For listing all ingredients in recipe
Else
score = 0
End If
ZADD ing:i score j
End For
End For
Code:
#!/usr/bin/php
<?
### Total of ingredients
define('NUM_OF_ING',12);
### Total of recipes
define('NUM_OF_RECIPES',600000);
$redis = new \Redis();
$redis->connect('localhost');
for ($ing=1; $ing<=NUM_OF_ING; $ing++) {
for ($recipe=1; $recipe<=NUM_OF_RECIPES; $recipe++) {
$score = rand() % 2;
if ($score == 1) {
$redis->incr("recipe:$recipe:ing_count");
$redis->rpush("recipe:$recipe:ing_list", $ing);
}
$redis->zAdd("ing:$ing", $score, $recipe);
}
}
echo "Done.\n";
?>
Querying
Before I paste the PHP code and measured running time, let me do some observations:
Sorting is done based on the number of ingredients used (sum of the zsets in query). If two recipes use all ingredients that are in query, then the tie-break is done by the number of additional ingredients that one recipe has. More ingredients, higher position.
The sum is handled by ZINTERSTORE. The zset with sums is stored in result.
Then a SORT command looks in the count key for each recipe, tailoring the order with this additional constraint.
Code:
#!/usr/bin/php
<?
$redis = new \Redis();
$redis->connect('localhost');
//properties in query
$query = array('ing:2', 'ing:4', 'ing:5');
$weights = array(1, 1, 1);
//intersection
$redis->zInter('result', $query, $weights, 'sum');
//sorting
echo "Result:\n";
var_dump($redis->sort('result', array('by'=>'recipe:*:ing_count', 'sort'=>'desc', 'limit'=>array(0,10))));
echo "End.\n";
?>
Output and running time:
niloct#Impulse-Ubuntu:~$ time ./final2.php
Result:
array(10) {
[0]=>
string(4) "5230"
[1]=>
string(5) "79549"
[2]=>
string(4) "2871"
[3]=>
string(3) "336"
[4]=>
string(6) "109279"
[5]=>
string(4) "5352"
[6]=>
string(5) "16868"
[7]=>
string(3) "690"
[8]=>
string(4) "3174"
[9]=>
string(4) "8795"
}
End.
real 0m2.930s
user 0m0.016s
sys 0m0.004s
niloct#Impulse-Ubuntu:~$ redis-cli lrange recipe:5230:ing_list 0 -1
1) "12"
2) "11"
3) "10"
4) "9"
5) "8"
6) "7"
7) "6"
8) "5"
9) "4"
10) "3"
11) "2"
12) "1"
Hope that helps.
PS: Can you post your performance measures after trying this ?